├── native └── LTCGenerator │ ├── .gitignore │ ├── CMakePresets.json │ ├── CMakeLists.txt │ └── src │ └── main.cpp ├── .eslintignore ├── www ├── assets │ ├── favicon.ico │ └── logo.svg ├── wasm │ ├── ltcGenerator.wasm │ └── LTCGenerator.js └── index.html ├── src ├── externalTypes.d.ts ├── ltc │ ├── LTCGenerator.ts │ └── ltcEffect.ts ├── blit │ ├── blit.glsl │ ├── blitEffect.ts │ ├── blitCubeEffect.ts │ └── blitCube.glsl ├── ibl │ ├── iblSpecularFragment.glsl │ ├── iblDiffuseFragment.glsl │ ├── iblDiffuseEffect.ts │ └── iblSpecularEffect.ts ├── brdf │ ├── brdfEffect.ts │ ├── makeBrdf.py │ └── brdfFragment.glsl ├── textureTools │ └── textureTools.ts └── app.ts ├── .editorconfig ├── .gitmodules ├── .gitignore ├── .eslintrc.js ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── webpack.prod.js ├── tsconfig.json ├── webpack.common.js ├── webpack.dev.js ├── LICENSE ├── README.md └── package.json /native/LTCGenerator/.gitignore: -------------------------------------------------------------------------------- 1 | [Bb]uild/ 2 | .vscode/ -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | www 4 | webpack.* -------------------------------------------------------------------------------- /www/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/BabylonjsTextureTools/HEAD/www/assets/favicon.ico -------------------------------------------------------------------------------- /www/wasm/ltcGenerator.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/BabylonjsTextureTools/HEAD/www/wasm/ltcGenerator.wasm -------------------------------------------------------------------------------- /src/externalTypes.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.glsl' { 2 | const shaderCode: string; 3 | 4 | export default shaderCode; 5 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = false -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "native/LTCGenerator/Dependencies/ltc_code"] 2 | path = native/LTCGenerator/Dependencies/ltc_code 3 | url = https://github.com/SergioRZMasson/ltc_code.git 4 | -------------------------------------------------------------------------------- /src/ltc/LTCGenerator.ts: -------------------------------------------------------------------------------- 1 | // my_module.d.ts 2 | declare const Module: { 3 | onRuntimeInitialized: () => void; 4 | HEAPF32: Float32Array; 5 | BuildLTC: any; 6 | }; 7 | export default Module; 8 | -------------------------------------------------------------------------------- /src/blit/blit.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUV; 2 | 3 | uniform float invertY; 4 | uniform sampler2D textureSampler; 5 | 6 | void main(void) 7 | { 8 | vec2 uv = vUV; 9 | if (invertY == 1.0) { 10 | uv.y = 1.0 - uv.y; 11 | } 12 | 13 | gl_FragColor = texture2D(textureSampler, uv); 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | # And OS or Editor folders 3 | [Bb]in/ 4 | *.tmp 5 | *.log 6 | *.DS_Store 7 | ._* 8 | Thumbs.db 9 | .cache 10 | .tmproj 11 | nbproject 12 | *.sublime-project 13 | *.sublime-workspace 14 | .idea 15 | .directory 16 | build 17 | .history 18 | .tempChromeProfileForDebug 19 | 20 | # Node Modules 21 | node_modules/* 22 | 23 | # Local dist 24 | dist/* 25 | www/scripts/* -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: [ 5 | '@typescript-eslint', 6 | ], 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | ], 12 | rules: { 13 | "@typescript-eslint/explicit-function-return-type": "error", 14 | "@typescript-eslint/no-unused-vars": "error", 15 | }, 16 | env: { 17 | 'browser': true 18 | }, 19 | }; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Index (Chrome)", 6 | "type": "chrome", 7 | "request": "launch", 8 | "url": "http://localhost:8080", 9 | "webRoot": "${workspaceRoot}", 10 | "sourceMaps": true, 11 | "preLaunchTask": "devServer", 12 | "userDataDir": "${workspaceRoot}/.tempChromeProfileForDebug", 13 | "runtimeArgs": [ 14 | "--enable-unsafe-es3-apis" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const ESLintPlugin = require('eslint-webpack-plugin'); 3 | const common = require('./webpack.common.js'); 4 | const path = require('path'); 5 | 6 | const DIST_DIR = path.resolve(__dirname, "./www"); 7 | 8 | module.exports = merge(common, { 9 | output: { 10 | path: DIST_DIR + "/scripts/", 11 | }, 12 | mode: 'production', 13 | devtool: false, 14 | module: { 15 | rules: [{ 16 | test: /\.tsx?$/, 17 | use: ["ts-loader"], 18 | }] 19 | }, 20 | plugins: [new ESLintPlugin()], 21 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esNext", 4 | "target": "ES2018", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "types" : [], 8 | "lib": ["dom", "es2015"], 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "skipLibCheck": true, 16 | "preserveConstEnums":true, 17 | "allowJs": true, 18 | "checkJs": true 19 | }, 20 | "include": [ 21 | "src/**/*" 22 | ], 23 | } -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | 4 | const SRC_DIR = path.resolve(__dirname, "./src"); 5 | 6 | module.exports = { 7 | context: __dirname, 8 | entry: { 9 | index: SRC_DIR + "/app.ts" 10 | }, 11 | output: { 12 | publicPath: "scripts/", 13 | filename: "[name].js", 14 | }, 15 | resolve: { 16 | extensions: [".ts", ".js"] 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(glsl)$/i, 22 | use: ["raw-loader"] 23 | }] 24 | }, 25 | plugins: [ 26 | new CleanWebpackPlugin() 27 | ] 28 | }; -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | const path = require('path'); 4 | 5 | const DEV_DIR = path.resolve(__dirname, "./.temp"); 6 | 7 | module.exports = merge(common, { 8 | output: { 9 | path: DEV_DIR + "/scripts/", 10 | publicPath: "/scripts/", 11 | }, 12 | mode: 'development', 13 | devtool: "source-map", 14 | devServer: { 15 | static: ['www'], 16 | compress: true, 17 | hot: true, 18 | // host: '0.0.0.0', // enable to access from other devices on the network 19 | // https: true // enable when HTTPS is needed 20 | }, 21 | module: { 22 | rules: [{ 23 | test: /\.tsx?$/, 24 | use: ["ts-loader"], 25 | }] 26 | }, 27 | }); -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.svn": true, 7 | "**/.hg": true, 8 | "**/.DS_Store": true, 9 | "**/.vs": true, 10 | "**/.tempChromeProfileForDebug": true, 11 | "**/node_modules": true, 12 | "**/.temp": true 13 | }, 14 | "search.exclude": { 15 | "**/.tempChromeProfileForDebug": true, 16 | "**/node_modules": true, 17 | "**/.temp": true, 18 | "**/.dist": true, 19 | "**/*.map": true, 20 | "**/scripts": true 21 | }, 22 | "editor.tabSize": 4, 23 | "typescript.tsdk": "node_modules\\typescript\\lib" 24 | } -------------------------------------------------------------------------------- /native/LTCGenerator/CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 20, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "wasm", 11 | "displayName": "WASM", 12 | "description": "WASM build.", 13 | "generator": "Ninja", 14 | "binaryDir": "${sourceDir}/build/WASM/", 15 | "condition": { 16 | "type": "equals", 17 | "lhs": "${hostSystemName}", 18 | "rhs": "Windows" 19 | }, 20 | "cacheVariables": { 21 | "CMAKE_TOOLCHAIN_FILE": "$env{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", 22 | "CMAKE_BUILD_TYPE": "Release" 23 | } 24 | } 25 | ], 26 | "buildPresets": [ 27 | { 28 | "name": "wasm", 29 | "configurePreset": "wasm", 30 | "targets": ["install"], 31 | "configuration": "Release" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 sebavan 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 | # Babylon.js Texture Tools 2 | 3 | [![Twitter](https://img.shields.io/twitter/follow/babylonjs.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=babylonjs) 4 | 5 | **Any questions?** Here is our official [forum](https://forum.babylonjs.com/). 6 | 7 | ## Running locally 8 | 9 | After cloning the repo, running locally during development is all the simplest: 10 | ``` 11 | npm install 12 | npm start 13 | ``` 14 | 15 | For VSCode users, if you have installed the Chrome Debugging extension, you can start debugging within VSCode by using the appropriate launch menu. 16 | 17 | ## To generate WASM for LTCGenerator 18 | 19 | In order to compile C++ to WASM you must fist install Emscripten (see [Install instruction](https://emscripten.org/docs/getting_started/downloads.html)). After completing the installation and configuration of Emscripten run the following commands: 20 | 21 | ``` 22 | cd native/LTCGenerator 23 | cmake --preset wasm 24 | cmake --build --preset wasm 25 | ``` 26 | 27 | The ```LTCGenerator.js``` and ```LTCGenerator.wasm``` will be copied into ```www/wasm``` folder. 28 | 29 | ## Special thanks 30 | To Filament and its team for the code and support :-) 31 | 32 | -------------------------------------------------------------------------------- /src/ibl/iblSpecularFragment.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUV; 2 | 3 | uniform samplerCube environmentMap; 4 | uniform vec2 textureInfo; 5 | uniform float face; 6 | 7 | uniform float linearRoughness; 8 | 9 | // PROD MODE 10 | #define NUM_SAMPLES 4096u 11 | // DEV MODE 12 | // #define NUM_SAMPLES 32u 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | void main() 20 | { 21 | float cx = vUV.x * 2. - 1.; 22 | float cy = (1. - vUV.y) * 2. - 1.; 23 | 24 | vec3 dir = vec3(0.); 25 | if (face == 0.) { // PX 26 | dir = vec3( 1., cy, -cx); 27 | } 28 | else if (face == 1.) { // NX 29 | dir = vec3(-1., cy, cx); 30 | } 31 | else if (face == 2.) { // PY 32 | dir = vec3( cx, 1., -cy); 33 | } 34 | else if (face == 3.) { // NY 35 | dir = vec3( cx, -1., cy); 36 | } 37 | else if (face == 4.) { // PZ 38 | dir = vec3( cx, cy, 1.); 39 | } 40 | else if (face == 5.) { // NZ 41 | dir = vec3(-cx, cy, -1.); 42 | } 43 | dir = normalize(dir); 44 | 45 | vec3 integratedBRDF = radiance(linearRoughness, environmentMap, dir, textureInfo); 46 | gl_FragColor = vec4(integratedBRDF, 1.); 47 | } -------------------------------------------------------------------------------- /src/ibl/iblDiffuseFragment.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUV; 2 | 3 | uniform samplerCube environmentMap; 4 | uniform sampler2D icdf; 5 | uniform vec2 textureInfo; 6 | uniform float face; 7 | 8 | // PROD MODE 9 | #define NUM_SAMPLES 4096u 10 | // DEV MODE 11 | // #define NUM_SAMPLES 32u 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | void main() 19 | { 20 | float cx = vUV.x * 2. - 1.; 21 | float cy = (1. - vUV.y) * 2. - 1.; 22 | 23 | vec3 dir = vec3(0.); 24 | if (face == 0.) { // PX 25 | dir = vec3( 1., cy, -cx); 26 | } 27 | else if (face == 1.) { // NX 28 | dir = vec3(-1., cy, cx); 29 | } 30 | else if (face == 2.) { // PY 31 | dir = vec3( cx, 1., -cy); 32 | } 33 | else if (face == 3.) { // NY 34 | dir = vec3( cx, -1., cy); 35 | } 36 | else if (face == 4.) { // PZ 37 | dir = vec3( cx, cy, 1.); 38 | } 39 | else if (face == 5.) { // NZ 40 | dir = vec3(-cx, cy, -1.); 41 | } 42 | dir = normalize(dir); 43 | 44 | vec3 integratedBRDF = irradiance(environmentMap, dir, textureInfo, 0.0, vec3(1.0), dir, icdf); 45 | 46 | gl_FragColor = vec4(integratedBRDF, 1.); 47 | } -------------------------------------------------------------------------------- /src/blit/blitEffect.ts: -------------------------------------------------------------------------------- 1 | import { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import { EffectWrapper, EffectRenderer } from "@babylonjs/core/Materials/effectRenderer"; 3 | import { InternalTexture } from "@babylonjs/core/Materials/Textures/internalTexture"; 4 | 5 | import blitFragment from "./blit.glsl"; 6 | import { RenderTargetWrapper } from "@babylonjs/core/Engines/renderTargetWrapper"; 7 | 8 | export class BlitEffect { 9 | private readonly _renderer: EffectRenderer; 10 | private readonly _blit: EffectWrapper; 11 | 12 | constructor(engine: ThinEngine, renderer: EffectRenderer) { 13 | this._renderer = renderer; 14 | this._blit = new EffectWrapper({ 15 | name: "Blit", 16 | engine: engine, 17 | fragmentShader: blitFragment, 18 | samplerNames: ["textureSampler"], 19 | uniformNames: ["invertY"], 20 | }); 21 | } 22 | 23 | public blit(rttOrTexture: InternalTexture | RenderTargetWrapper, invertY = false): void { 24 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 25 | const texture = rttOrTexture instanceof RenderTargetWrapper ? rttOrTexture.texture! : rttOrTexture; 26 | this._blit.onApplyObservable.addOnce(() => { 27 | this._blit.effect.setFloat("invertY", invertY ? 1.0 : 0.0); 28 | this._blit.effect._bindTexture("textureSampler", texture); 29 | }) 30 | this._renderer.render(this._blit); 31 | } 32 | } -------------------------------------------------------------------------------- /src/blit/blitCubeEffect.ts: -------------------------------------------------------------------------------- 1 | import { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import { EffectWrapper, EffectRenderer } from "@babylonjs/core/Materials/effectRenderer"; 3 | import { InternalTexture } from "@babylonjs/core/Materials/Textures/internalTexture"; 4 | 5 | import "@babylonjs/core/Shaders/ShadersInclude/helperFunctions"; 6 | 7 | import blitFragment from "./blitCube.glsl"; 8 | import { RenderTargetWrapper } from "@babylonjs/core/Engines/renderTargetWrapper"; 9 | 10 | export class BlitCubeEffect { 11 | private readonly _renderer: EffectRenderer; 12 | private readonly _blit: EffectWrapper; 13 | 14 | constructor(engine: ThinEngine, renderer: EffectRenderer) { 15 | this._renderer = renderer; 16 | this._blit = new EffectWrapper({ 17 | name: "BlitCube", 18 | engine: engine, 19 | fragmentShader: blitFragment, 20 | samplerNames: ["textureSampler"], 21 | uniformNames: ["lod"], 22 | }); 23 | } 24 | 25 | public blit(rttOrTexture: InternalTexture | RenderTargetWrapper, lod: number): void { 26 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 27 | const texture = rttOrTexture instanceof RenderTargetWrapper ? rttOrTexture.texture! : rttOrTexture; 28 | this._blit.onApplyObservable.addOnce(() => { 29 | this._blit.effect.setFloat("lod", lod); 30 | this._blit.effect._bindTexture("textureSampler", texture); 31 | }) 32 | this._renderer.render(this._blit); 33 | } 34 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@babylonjs/texture-tools", 3 | "version": "1.0.0", 4 | "description": "Babylonjs texture tools to generate pre-filtered environments and BRDF lookups.", 5 | "author": { 6 | "name": "Sebastien Vandenberghe" 7 | }, 8 | "contributors": [ 9 | "Sebastien Vandenberghe" 10 | ], 11 | "keywords": [ 12 | "PBR", 13 | "Tools", 14 | "Texture", 15 | "BRDF", 16 | "3D", 17 | "IBL", 18 | "javascript", 19 | "html5", 20 | "webgl", 21 | "webgl2", 22 | "webgpu", 23 | "babylon" 24 | ], 25 | "license": "MIT", 26 | "readme": "README.md", 27 | "typings": "dist/src/index.d.ts", 28 | "main": "dist/src/index.js", 29 | "files": [ 30 | "dist/src/**", 31 | "README.md", 32 | "LICENSE" 33 | ], 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/sebavan/BabylonjsTextureTools.git" 37 | }, 38 | "scripts": { 39 | "lint": "npx eslint . --ext .ts,.tsx", 40 | "build": "npx webpack --config webpack.prod.js", 41 | "start": "npx webpack-dev-server --open --config webpack.dev.js", 42 | "devServer": "npx webpack-dev-server --config webpack.dev.js" 43 | }, 44 | "dependencies": { 45 | "@babylonjs/core": "^8.12.1" 46 | }, 47 | "devDependencies": { 48 | "@typescript-eslint/eslint-plugin": "^8.16.0", 49 | "@typescript-eslint/parser": "^8.16.0", 50 | "clean-webpack-plugin": "^4.0.0", 51 | "eslint": "^9.15.0", 52 | "eslint-webpack-plugin": "^4.2.0", 53 | "file-loader": "^6.2.0", 54 | "raw-loader": "^4.0.2", 55 | "source-map-loader": "^5.0.0", 56 | "ts-loader": "^9.5.1", 57 | "typescript": "^5.5.0", 58 | "webpack": "^5.96.1", 59 | "webpack-cli": "^5.1.4", 60 | "webpack-dev-server": "^5.2.1", 61 | "webpack-merge": "^6.0.1" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /native/LTCGenerator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(LTCGenerator) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | set(LTC_GENERATOR_SOURCE 8 | "Dependencies/ltc_code/fit/dds.cpp" 9 | "src/main.cpp") 10 | 11 | set(LTC_GENERATOR_HEADERS 12 | "Dependencies/ltc_code/fit/brdf_beckmann.h" 13 | "Dependencies/ltc_code/fit/brdf_disneyDiffuse.h" 14 | "Dependencies/ltc_code/fit/brdf_ggx.h" 15 | "Dependencies/ltc_code/fit/brdf.h" 16 | "Dependencies/ltc_code/fit/dds.h" 17 | "Dependencies/ltc_code/fit/export.h" 18 | "Dependencies/ltc_code/fit/float_to_half.h" 19 | "Dependencies/ltc_code/fit/LTC.h" 20 | "Dependencies/ltc_code/fit/nelder_mead.h" 21 | "Dependencies/ltc_code/fit/plot.h") 22 | 23 | add_executable(LTCGenerator ${LTC_GENERATOR_SOURCE} ${LTC_GENERATOR_HEADERS}) 24 | target_include_directories(LTCGenerator PRIVATE "src") 25 | target_include_directories(LTCGenerator PRIVATE "Dependencies/ltc_code/fit/") 26 | 27 | if(DEFINED EMSCRIPTEN) 28 | target_link_options(LTCGenerator PRIVATE "-s NO_EXIT_RUNTIME=1") 29 | target_link_options(LTCGenerator PRIVATE "-s WASM=1") 30 | 31 | # Required for ES6 32 | target_link_options(LTCGenerator PRIVATE "-s ENVIRONMENT='web,worker'") 33 | target_link_options(LTCGenerator PRIVATE "-s STACK_SIZE=4000000") 34 | target_link_options(LTCGenerator PRIVATE "-s ASSERTIONS") 35 | target_link_options(LTCGenerator PRIVATE "-s ALLOW_MEMORY_GROWTH=1") 36 | target_link_options(LTCGenerator PRIVATE "-s MODULARIZE=1") 37 | target_link_options(LTCGenerator PRIVATE "-s EXPORT_ES6") 38 | target_link_options(LTCGenerator PRIVATE "-s INVOKE_RUN=0") 39 | target_link_options(LTCGenerator PRIVATE "--bind") 40 | 41 | target_compile_options(LTCGenerator PRIVATE -O3) 42 | #target_link_options(LTCGenerator PRIVATE "-s SIDE_MODULE=1") 43 | endif() 44 | 45 | target_include_directories(LTCGenerator PUBLIC "Dependencies/ltc_code/external/glm") 46 | 47 | install(FILES 48 | "$/LTCGenerator.js" 49 | "$/LTCGenerator.wasm" 50 | DESTINATION "${PROJECT_SOURCE_DIR}/../../www/wasm") -------------------------------------------------------------------------------- /src/blit/blitCube.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUV; 2 | 3 | uniform float lod; 4 | uniform samplerCube textureSampler; 5 | 6 | #include 7 | 8 | void main(void) 9 | { 10 | float cx = vUV.x * 2. - 1.; 11 | float cy = vUV.y * 2. - 1.; 12 | 13 | float face = 0.; 14 | if (cy > 0.75) { 15 | gl_FragColor = vec4(0.); 16 | return; 17 | } 18 | else if (cy > 0.25) { 19 | if (cx > 0. && cx < 0.5) { 20 | face = 2.; 21 | cx = cx * 4. - 1.; 22 | cy = cy * 4. - 2.; 23 | } 24 | else { 25 | gl_FragColor = vec4(0.); 26 | return; 27 | } 28 | } 29 | else if (cy > -0.25) { 30 | cy = cy * 4.; 31 | if (cx > 0.5) { 32 | face = 0.; 33 | cx = cx * 4. - 3.; 34 | } 35 | else if (cx > 0.) { 36 | face = 4.; 37 | cx = cx * 4. - 1.; 38 | } 39 | else if (cx > -0.5) { 40 | face = 1.; 41 | cx = cx * 4. + 1.; 42 | } 43 | else { 44 | face = 5.; 45 | cx = cx * 4. + 3.; 46 | } 47 | } 48 | else if (cy > -0.75) { 49 | if (cx > 0. && cx < 0.5) { 50 | face = 3.; 51 | cx = cx * 4. - 1.; 52 | cy = cy * 4. + 2.; 53 | } 54 | else { 55 | gl_FragColor = vec4(0.); 56 | return; 57 | } 58 | } 59 | else { 60 | gl_FragColor = vec4(0.); 61 | return; 62 | } 63 | 64 | vec3 dir = vec3(0.); 65 | if (face == 0.) { // PX 66 | dir = vec3( 1., cy, -cx); 67 | } 68 | else if (face == 1.) { // NX 69 | dir = vec3(-1., cy, cx); 70 | } 71 | else if (face == 2.) { // PY 72 | dir = vec3( cx, 1., -cy); 73 | } 74 | else if (face == 3.) { // NY 75 | dir = vec3( cx, -1., cy); 76 | } 77 | else if (face == 4.) { // PZ 78 | dir = vec3( cx, cy, 1.); 79 | } 80 | else if (face == 5.) { // NZ 81 | dir = vec3(-cx, cy, -1.); 82 | } 83 | dir = normalize(dir); 84 | 85 | // AND YES IT IS THE LEAST OPTIMIZE I COULD THINK OF... 86 | // but it works :-) 87 | // I keep it only for posterity and cause it is not used in real time. 88 | 89 | vec3 c = textureCubeLodEXT(textureSampler, dir, lod).rgb; 90 | gl_FragColor = vec4(toGammaSpace(c), 1.); 91 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "command": "npm", 6 | "type": "shell", 7 | "presentation": { 8 | "echo": true, 9 | "reveal": "always", 10 | "focus": false, 11 | "panel": "shared" 12 | }, 13 | "tasks": [ 14 | { 15 | "label": "start", 16 | "args": [ "start" ], 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true 20 | }, 21 | "isBackground": true, 22 | "problemMatcher": { 23 | "owner": "typescript", 24 | "fileLocation": "relative", 25 | "pattern": { 26 | "regexp": "^([^\\s].*)\\((\\d+|\\,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", 27 | "file": 1, 28 | "location": 2, 29 | "severity": 3, 30 | "code": 4, 31 | "message": 5 32 | }, 33 | "background": { 34 | "activeOnStart": true, 35 | "beginsPattern": "start", 36 | "endsPattern": "Compiled successfully" 37 | } 38 | } 39 | }, 40 | { 41 | "label": "devServer", 42 | "args": [ "run", "devServer" ], 43 | "group": { 44 | "kind": "build", 45 | "isDefault": true 46 | }, 47 | "isBackground": true, 48 | "problemMatcher": { 49 | "owner": "typescript", 50 | "fileLocation": "relative", 51 | "pattern": { 52 | "regexp": "^([^\\s].*)\\((\\d+|\\,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", 53 | "file": 1, 54 | "location": 2, 55 | "severity": 3, 56 | "code": 4, 57 | "message": 5 58 | }, 59 | "background": { 60 | "activeOnStart": true, 61 | "beginsPattern": "devServer", 62 | "endsPattern": "Compiled successfully" 63 | } 64 | } 65 | } 66 | ] 67 | } -------------------------------------------------------------------------------- /src/ltc/ltcEffect.ts: -------------------------------------------------------------------------------- 1 | import "@babylonjs/core/Engines/Extensions/engine.renderTarget"; 2 | import "@babylonjs/core/Engines/Extensions/engine.renderTargetCube"; 3 | 4 | import "@babylonjs/core/Shaders/ShadersInclude/helperFunctions"; 5 | import "@babylonjs/core/Shaders/ShadersInclude/pbrHelperFunctions"; 6 | import "@babylonjs/core/Shaders/ShadersInclude/pbrBRDFFunctions"; 7 | import "@babylonjs/core/Shaders/ShadersInclude/hdrFilteringFunctions"; 8 | import "@babylonjs/core/Shaders/ShadersInclude/importanceSampling"; 9 | import "@babylonjs/core/Maths/math.vector"; 10 | import Module from "./LTCGenerator"; 11 | import { Nullable } from "@babylonjs/core/types"; 12 | import { ToHalfFloat } from "@babylonjs/core/Misc/textureTools"; 13 | import { Tools } from "@babylonjs/core/Misc/tools"; 14 | 15 | export class LTCEffect { 16 | constructor( 17 | private N: number, 18 | private Nsample: number, 19 | private MIN_ALPHA: number 20 | ) {} 21 | 22 | public render(): Nullable { 23 | if (Module) { 24 | const float32Array = Module.BuildLTC( 25 | this.N, 26 | this.Nsample, 27 | this.MIN_ALPHA 28 | ); 29 | const numElements = float32Array.length; 30 | const ptr = float32Array.byteOffset; 31 | const array = new Float32Array( 32 | Module.HEAPF32.buffer, 33 | ptr, 34 | numElements 35 | ); 36 | return array; 37 | } 38 | 39 | return null; 40 | } 41 | 42 | public save(data: Float32Array): void { 43 | const dataSizeHalf = this.N * this.N * 4; 44 | // Array with 6 channels per pixel. 45 | const result = new Uint16Array(this.N * this.N * 8); 46 | 47 | for (let pixelIndex = 0; pixelIndex < this.N * this.N; pixelIndex++) { 48 | // LTC1 R value 49 | result[pixelIndex * 8] = ToHalfFloat(data[pixelIndex * 4]); 50 | 51 | // LTC1 G value 52 | result[pixelIndex * 8 + 1] = ToHalfFloat(data[pixelIndex * 4 + 1]); 53 | 54 | // LTC1 B value 55 | result[pixelIndex * 8 + 2] = ToHalfFloat(data[pixelIndex * 4 + 2]); 56 | 57 | // LTC1 A value 58 | result[pixelIndex * 8 + 3] = ToHalfFloat(data[pixelIndex * 4 + 3]); 59 | 60 | // LTC2 R value 61 | result[pixelIndex * 8 + 4] = ToHalfFloat(data[(pixelIndex * 4) + dataSizeHalf]); 62 | 63 | // LTC2 G value 64 | result[pixelIndex * 8 + 5] = ToHalfFloat(data[(pixelIndex * 4 + 1) + dataSizeHalf]); 65 | 66 | // LTC2 B value 67 | result[pixelIndex * 8 + 6] = ToHalfFloat(data[(pixelIndex * 4 + 2) + dataSizeHalf]); 68 | 69 | // LTC2 A value 70 | result[pixelIndex * 8 + 7] = ToHalfFloat(data[(pixelIndex * 4 + 3) + dataSizeHalf]); 71 | } 72 | 73 | console.log(result); 74 | Tools.DownloadBlob(new Blob([result.buffer]), "areaLightsLTC.bin"); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /www/assets/logo.svg: -------------------------------------------------------------------------------- 1 | babylonjs_identity_color_dark -------------------------------------------------------------------------------- /src/ibl/iblDiffuseEffect.ts: -------------------------------------------------------------------------------- 1 | import { EffectWrapper, EffectRenderer } from "@babylonjs/core/Materials/effectRenderer"; 2 | import { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 3 | import { BaseTexture } from "@babylonjs/core/Materials/Textures/baseTexture"; 4 | import { Constants } from "@babylonjs/core/Engines/constants"; 5 | import { Scalar } from "@babylonjs/core/Maths/math.scalar"; 6 | import { RenderTargetWrapper } from "@babylonjs/core/Engines/renderTargetWrapper"; 7 | 8 | import "@babylonjs/core/Engines/Extensions/engine.renderTarget"; 9 | import "@babylonjs/core/Engines/Extensions/engine.renderTargetCube"; 10 | 11 | import "@babylonjs/core/Shaders/ShadersInclude/helperFunctions"; 12 | import "@babylonjs/core/Shaders/ShadersInclude/pbrHelperFunctions"; 13 | import "@babylonjs/core/Shaders/ShadersInclude/pbrBRDFFunctions"; 14 | import "@babylonjs/core/Shaders/ShadersInclude/hdrFilteringFunctions"; 15 | import "@babylonjs/core/Shaders/ShadersInclude/importanceSampling"; 16 | 17 | import fragmentShader from "./iblDiffuseFragment.glsl"; 18 | 19 | export class IBLDiffuseEffect { 20 | public readonly rtw: RenderTargetWrapper; 21 | 22 | private readonly _engine: ThinEngine; 23 | private readonly _effectRenderer: EffectRenderer; 24 | 25 | constructor(engine: ThinEngine, effectRenderer: EffectRenderer, size = 512) { 26 | this._engine = engine; 27 | this._effectRenderer = effectRenderer; 28 | 29 | this.rtw = this._createRenderTarget(size); 30 | } 31 | 32 | public render(texture: BaseTexture, icdf: BaseTexture): void { 33 | const effectWrapper = this._getEffect(texture); 34 | 35 | this._effectRenderer.setViewport(); 36 | 37 | const intTexture = texture.getInternalTexture(); 38 | if (intTexture) { 39 | // Just in case generate fresh clean mips. 40 | this._engine.updateTextureSamplingMode(Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, intTexture, true); 41 | } 42 | 43 | this._effectRenderer.applyEffectWrapper(effectWrapper); 44 | 45 | const textureWidth = texture.getSize().width; 46 | const mipmapsCount = Math.round(Scalar.Log2(textureWidth)) + 1; 47 | effectWrapper.effect.setTexture("environmentMap", texture); 48 | effectWrapper.effect.setTexture("icdf", icdf); 49 | effectWrapper.effect.setFloat2("textureInfo", textureWidth, mipmapsCount - 1); 50 | 51 | for (let face = 0; face < 6; face++) { 52 | this._engine.bindFramebuffer(this.rtw, face); 53 | 54 | this._effectRenderer.applyEffectWrapper(effectWrapper); 55 | 56 | effectWrapper.effect.setFloat("face", face); 57 | 58 | this._effectRenderer.draw(); 59 | } 60 | 61 | this._engine.unBindFramebuffer(this.rtw); 62 | 63 | 64 | effectWrapper.dispose(); 65 | } 66 | 67 | private _getEffect(texture: BaseTexture): EffectWrapper { 68 | const defines = []; 69 | if (texture.gammaSpace) { 70 | defines.push("#define GAMMA_INPUT"); 71 | } 72 | defines.push("#define IBL_CDF_FILTERING"); 73 | 74 | const shader = defines.join("\n") + "\n" + fragmentShader; 75 | 76 | const effectWrapper = new EffectWrapper({ 77 | engine: this._engine, 78 | name: "IBLDiffuse", 79 | fragmentShader: shader, 80 | samplerNames: ["environmentMap", "icdf"], 81 | uniformNames: ["textureInfo", "face"], 82 | }); 83 | 84 | return effectWrapper; 85 | } 86 | 87 | private _createRenderTarget(size: number): RenderTargetWrapper { 88 | const rtw = this._engine.createRenderTargetCubeTexture(size, { 89 | format: Constants.TEXTUREFORMAT_RGBA, 90 | type: Constants.TEXTURETYPE_FLOAT, 91 | generateMipMaps: false, 92 | generateDepthBuffer: false, 93 | generateStencilBuffer: false, 94 | samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE 95 | }); 96 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 97 | this._engine.updateTextureWrappingMode(rtw.texture!, 98 | Constants.TEXTURE_CLAMP_ADDRESSMODE, 99 | Constants.TEXTURE_CLAMP_ADDRESSMODE, 100 | Constants.TEXTURE_CLAMP_ADDRESSMODE); 101 | 102 | return rtw; 103 | } 104 | } -------------------------------------------------------------------------------- /src/brdf/brdfEffect.ts: -------------------------------------------------------------------------------- 1 | import { EffectWrapper, EffectRenderer } from "@babylonjs/core/Materials/effectRenderer"; 2 | import { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 3 | import { Constants } from "@babylonjs/core/Engines/constants"; 4 | import { Tools } from "@babylonjs/core/Misc/tools"; 5 | 6 | import "@babylonjs/core/Engines/Extensions/engine.renderTarget"; 7 | 8 | import "@babylonjs/core/Shaders/ShadersInclude/helperFunctions"; 9 | import "@babylonjs/core/Shaders/ShadersInclude/pbrHelperFunctions"; 10 | import "@babylonjs/core/Shaders/ShadersInclude/pbrBRDFFunctions"; 11 | import "@babylonjs/core/Shaders/ShadersInclude/importanceSampling"; 12 | import "@babylonjs/core/Shaders/ShadersInclude/hdrFilteringFunctions"; 13 | 14 | import { BlitEffect } from "../blit/blitEffect"; 15 | 16 | import fragmentShader from "./brdfFragment.glsl"; 17 | import { RenderTargetWrapper } from "@babylonjs/core"; 18 | 19 | export enum BRDFMode { 20 | CorrelatedGGXEnergieConservation, 21 | CorrelatedGGX, 22 | UncorrelatedGGX, 23 | } 24 | 25 | export class BRDFEffect { 26 | public readonly rtw: RenderTargetWrapper; 27 | 28 | private readonly _size: number; 29 | private readonly _engine: ThinEngine; 30 | private readonly _effectRenderer: EffectRenderer; 31 | private readonly _blitEffect: BlitEffect; 32 | 33 | constructor(engine: ThinEngine, effectRenderer: EffectRenderer, size = 256) { 34 | this._size = size; 35 | this._engine = engine; 36 | this._effectRenderer = effectRenderer; 37 | this._blitEffect = new BlitEffect(this._engine, this._effectRenderer); 38 | 39 | this.rtw = this._createRenderTarget(size); 40 | } 41 | 42 | public render(mode: BRDFMode, sheen: boolean, rgbd = false): void { 43 | const effectWrapper = this._getEffect(mode, sheen, rgbd); 44 | 45 | this._effectRenderer.render(effectWrapper, this.rtw); 46 | effectWrapper.dispose(); 47 | } 48 | 49 | public save(mode: BRDFMode, sheen: boolean): void { 50 | const canvas = this._engine.getRenderingCanvas(); 51 | if (!canvas) { 52 | return; 53 | } 54 | 55 | this.render(mode, sheen, true); 56 | 57 | const oldWidth = this._engine.getRenderWidth(); 58 | const oldHeight = this._engine.getRenderWidth(); 59 | 60 | this._engine.setSize(this._size, this._size); 61 | 62 | if (this.rtw.texture) { 63 | this._blitEffect.blit(this.rtw.texture, true); 64 | } 65 | 66 | // Reading datas from WebGL 67 | Tools.ToBlob(canvas, (blob) => { 68 | if (blob) { 69 | Tools.Download(blob, "rgbdBrdfLookup.png"); 70 | } 71 | }); 72 | 73 | this._engine.setSize(oldWidth, oldHeight); 74 | } 75 | 76 | private _getEffect(mode: BRDFMode, sheen: boolean, rgbd: boolean): EffectWrapper { 77 | const defines = []; 78 | if (rgbd) { 79 | defines.push("#define RGBD"); 80 | } 81 | if (sheen) { 82 | defines.push("#define SHEEN"); 83 | } 84 | switch(mode) { 85 | case BRDFMode.CorrelatedGGXEnergieConservation: 86 | defines.push("#define BRDF_V_HEIGHT_CORRELATED"); 87 | defines.push("#define MS_BRDF_ENERGY_CONSERVATION"); 88 | break; 89 | case BRDFMode.CorrelatedGGX: 90 | defines.push("#define BRDF_V_HEIGHT_CORRELATED"); 91 | break; 92 | case BRDFMode.UncorrelatedGGX: 93 | // no special defines 94 | break; 95 | } 96 | 97 | const shader = defines.join("\n") + "\n" + fragmentShader; 98 | 99 | const effectWrapper = new EffectWrapper({ 100 | engine: this._engine, 101 | name: "BRDF", 102 | fragmentShader: shader 103 | }); 104 | 105 | return effectWrapper; 106 | } 107 | 108 | private _createRenderTarget(size: number): RenderTargetWrapper { 109 | const rtw = this._engine.createRenderTargetTexture(size, { 110 | format: Constants.TEXTUREFORMAT_RGBA, 111 | type: Constants.TEXTURETYPE_FLOAT, 112 | generateMipMaps: false, 113 | generateDepthBuffer: false, 114 | generateStencilBuffer: false, 115 | samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE 116 | }); 117 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 118 | this._engine.updateTextureWrappingMode(rtw.texture!, 119 | Constants.TEXTURE_CLAMP_ADDRESSMODE, 120 | Constants.TEXTURE_CLAMP_ADDRESSMODE, 121 | Constants.TEXTURE_CLAMP_ADDRESSMODE); 122 | 123 | return rtw; 124 | } 125 | } -------------------------------------------------------------------------------- /src/textureTools/textureTools.ts: -------------------------------------------------------------------------------- 1 | import { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import { Engine } from "@babylonjs/core/Engines/engine"; 3 | import { EffectRenderer } from "@babylonjs/core/Materials/effectRenderer"; 4 | import { BaseTexture } from "@babylonjs/core/Materials/Textures/baseTexture"; 5 | import { IblCdfGenerator } from "@babylonjs/core/Rendering/iblCdfGenerator"; 6 | import { Color4 } from "@babylonjs/core/Maths/math.color"; 7 | import { BlitEffect } from "../blit/blitEffect"; 8 | import { BlitCubeEffect } from "../blit/blitCubeEffect"; 9 | import { BRDFEffect, BRDFMode } from "../brdf/brdfEffect"; 10 | import { IBLDiffuseEffect } from "../ibl/iblDiffuseEffect"; 11 | import { IBLSpecularEffect } from "../ibl/iblSpecularEffect"; 12 | import { LTCEffect } from "../ltc/ltcEffect"; 13 | import { Nullable } from "@babylonjs/core/types"; 14 | 15 | export interface BRDFOptions { 16 | size: number; 17 | } 18 | 19 | const ClearColor = new Color4(0.0, 0.0, 0.0, 1.0); 20 | 21 | /** 22 | * The canvas is responsible to create and orchestrate all the resources 23 | * the texture tools need (scene, camera...) 24 | */ 25 | export class TextureTools { 26 | public readonly engine: ThinEngine; 27 | 28 | private readonly _renderer: EffectRenderer; 29 | private readonly _blitEffect: BlitEffect; 30 | private readonly _blitCubeEffect: BlitCubeEffect; 31 | 32 | private readonly _brdfEffect: BRDFEffect; 33 | private readonly _iblDiffuseEffect: IBLDiffuseEffect; 34 | private readonly _iblSpecularEffect: IBLSpecularEffect; 35 | private readonly _ltcEffect: LTCEffect; 36 | private readonly _cdfGenerator: IblCdfGenerator; 37 | 38 | /** 39 | * Creates an instance of the texture tools associated to a html canvas element 40 | * @param canvas defines the html element to transform into and ink surface 41 | */ 42 | constructor(canvas: HTMLCanvasElement) { 43 | this.engine = this._createEngine(canvas); 44 | this.engine.getCaps().parallelShaderCompile = undefined; 45 | 46 | this._renderer = new EffectRenderer(this.engine); 47 | this._blitEffect = new BlitEffect(this.engine, this._renderer); 48 | this._blitCubeEffect = new BlitCubeEffect(this.engine, this._renderer); 49 | this._iblDiffuseEffect = new IBLDiffuseEffect(this.engine, this._renderer); 50 | this._iblSpecularEffect = new IBLSpecularEffect(this.engine, this._renderer); 51 | this._ltcEffect = new LTCEffect(64, 32, 0.0001); 52 | this._cdfGenerator = new IblCdfGenerator(this.engine); 53 | this._brdfEffect = new BRDFEffect(this.engine, this._renderer); 54 | } 55 | 56 | /** 57 | * Renders our BRDF texture. 58 | */ 59 | public renderBRDF(mode: BRDFMode, sheen: boolean): void { 60 | this._brdfEffect.render(mode, sheen); 61 | this._blitEffect.blit(this._brdfEffect.rtw); 62 | } 63 | 64 | /** 65 | * Saves our BRDF texture. 66 | */ 67 | public saveBRDF(mode: BRDFMode, sheen: boolean): void { 68 | this._brdfEffect.save(mode, sheen); 69 | 70 | this._blitEffect.blit(this._brdfEffect.rtw); 71 | } 72 | 73 | public renderLTC() : Nullable { 74 | return this._ltcEffect.render(); 75 | } 76 | 77 | public saveLTC(ltc: Float32Array) : void { 78 | this._ltcEffect.save(ltc); 79 | } 80 | 81 | /** 82 | * Renders our IBL Diffuse texture. 83 | */ 84 | public renderDiffuseIBL(texture: BaseTexture): void { 85 | this._cdfGenerator.onGeneratedObservable.addOnce(() => { 86 | this._iblDiffuseEffect.render(texture, this._cdfGenerator.getIcdfTexture()); 87 | this._blitCubeEffect.blit(this._iblDiffuseEffect.rtw, 0); 88 | }); 89 | this._cdfGenerator.iblSource = texture; 90 | this._cdfGenerator.renderWhenReady(); 91 | } 92 | 93 | public blitSpecularIBL(lod: number): void { 94 | if (!this._iblSpecularEffect.rtw) { 95 | return; 96 | } 97 | this._blitCubeEffect.blit(this._iblSpecularEffect.rtw, lod); 98 | } 99 | 100 | /** 101 | * Saves our IBL Specular texture. 102 | */ 103 | public saveSpecularIBL(texture: BaseTexture, size: number): void { 104 | this._iblSpecularEffect.save(texture, size); 105 | 106 | this._blitCubeEffect.blit(this._iblSpecularEffect.rtw, 0); 107 | } 108 | 109 | public clear(): void { 110 | this.engine.clear(ClearColor, true, true, true); 111 | } 112 | 113 | private _createEngine(canvas: HTMLCanvasElement): ThinEngine { 114 | // Create our engine to hold on the canvas 115 | const engine = new Engine(canvas, true, { 116 | preserveDrawingBuffer: true, 117 | premultipliedAlpha: false, 118 | alpha: true, 119 | }); 120 | return engine; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/brdf/makeBrdf.py: -------------------------------------------------------------------------------- 1 | # This file is written to parse the BRDF sheen LUT from the C++ source of 2 | # "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines" 3 | # https://github.com/tizian/ltc-sheen?tab=readme-ov-file 4 | # LUT file: https://github.com/MiiBond/ltc-sheen/blob/master/fitting/python/data/ltc_table_sheen_volume.cpp 5 | # LUT Generation Code: https://github.com/MiiBond/ltc-sheen/blob/master/fitting/src/bsdfs/sheen_volume.h 6 | #!/usr/bin/env python3 7 | """ 8 | sheen_lut_to_datauri.py 9 | 10 | Parse a C++ file containing: 11 | const Vector3f SheenLTC::_ltcParamTableApprox[32][32] = { { Vector3f(...), ... }, ... }; 12 | 13 | and emit a 32x32 PNG or data:image/png;base64 string. 14 | Now includes the option to flip the X/Y axes (transpose image). 15 | 16 | Usage: 17 | python3 sheen_lut_to_datauri.py input.cpp [--mode normalize|clip] [--output base64|png] 18 | Dependencies: 19 | pip install pillow 20 | """ 21 | import re 22 | import sys 23 | import argparse 24 | import base64 25 | from io import BytesIO 26 | from pathlib import Path 27 | 28 | try: 29 | from PIL import Image 30 | except Exception: 31 | print("Pillow is required. Install with: pip install pillow", file=sys.stderr) 32 | sys.exit(1) 33 | 34 | WIDTH = 32 35 | HEIGHT = 32 36 | EXPECTED = WIDTH * HEIGHT 37 | 38 | VECTOR_RE = re.compile(r'Vector3f\s*\(\s*([^\)]*?)\s*\)', re.S) 39 | FLOAT_RE = re.compile(r'[-+]?\d*\.\d+(?:[eE][-+]?\d+)?|[-+]?\d+') 40 | 41 | def parse_vector_entries(text): 42 | entries = VECTOR_RE.findall(text) 43 | vals = [] 44 | for ent in entries: 45 | nums = FLOAT_RE.findall(ent) 46 | if len(nums) < 3: 47 | continue 48 | x, y, z = float(nums[0]), float(nums[1]), float(nums[2]) 49 | vals.append((x, y, z)) 50 | return vals 51 | 52 | def to_png_data(pixels, width, height): 53 | img = Image.new("RGB", (width, height)) 54 | img.putdata(pixels) 55 | buf = BytesIO() 56 | img.save(buf, format="PNG") 57 | return buf.getvalue() 58 | 59 | def main(): 60 | parser = argparse.ArgumentParser(description="Convert Vector3f LUT to base64 PNG or PNG file") 61 | parser.add_argument("input", help="C++ file (or - for stdin)") 62 | parser.add_argument("-m", "--mode", choices=("normalize","clip"), default="normalize", 63 | help="normalize = per-channel remap to full 0..255 (default); clip = clamp to [0,1]") 64 | parser.add_argument("-o", "--output", choices=("base64","png"), default="base64", 65 | help="output format: base64 (default) or png (write file)") 66 | parser.add_argument("--outfile", default="ltc_sheen_lut.png", help="output PNG filename (if --output png)") 67 | parser.add_argument("--swapxy", action="store_true", help="swap X and Y axes of the LUT (transpose image)") 68 | args = parser.parse_args() 69 | 70 | src = sys.stdin.read() if args.input == "-" else Path(args.input).read_text(encoding="utf-8") 71 | 72 | vectors = parse_vector_entries(src) 73 | if len(vectors) != EXPECTED: 74 | print(f"Warning: found {len(vectors)} entries (expected {EXPECTED}).", file=sys.stderr) 75 | if len(vectors) < EXPECTED: 76 | sys.exit(1) 77 | vectors = vectors[:EXPECTED] 78 | 79 | rs = [v[0] for v in vectors] 80 | gs = [v[1] for v in vectors] 81 | bs = [v[2] for v in vectors] 82 | rmin, rmax = min(rs), max(rs) 83 | gmin, gmax = min(gs), max(gs) 84 | bmin, bmax = min(bs), max(bs) 85 | 86 | # reshape into 2D array [y][x] 87 | rows = [vectors[y*WIDTH:(y+1)*WIDTH] for y in range(HEIGHT)] 88 | 89 | # optionally transpose 90 | if args.swapxy: 91 | rows = list(map(list, zip(*rows))) # transpose 32x32 92 | 93 | pixels = [] 94 | if args.mode == "normalize": 95 | def norm(v, mn, mx): 96 | if mx <= mn: return 0 97 | return int(round(max(0, min(1, (v - mn) / (mx - mn))) * 255)) 98 | for y in range(HEIGHT): 99 | for x in range(WIDTH): 100 | r,g,b = rows[y][x] 101 | pixels.append((norm(r,rmin,rmax), norm(g,gmin,gmax), norm(b,bmin,bmax))) 102 | else: 103 | def clip8(v): return int(round(max(0, min(1, v)) * 255)) 104 | for y in range(HEIGHT): 105 | for x in range(WIDTH): 106 | r,g,b = rows[y][x] 107 | pixels.append((clip8(r), clip8(g), clip8(b))) 108 | 109 | png_bytes = to_png_data(pixels, WIDTH, HEIGHT) 110 | 111 | if args.output == "png": 112 | Path(args.outfile).write_bytes(png_bytes) 113 | print(f"✅ Wrote PNG: {args.outfile} ({WIDTH}x{HEIGHT})") 114 | else: 115 | data_uri = "data:image/png;base64," + base64.b64encode(png_bytes).decode("ascii") 116 | print("# Mode:", args.mode) 117 | print(f"# R range: {rmin:.6g}–{rmax:.6g}") 118 | print(f"# G range: {gmin:.6g}–{gmax:.6g}") 119 | print(f"# B range: {bmin:.6g}–{bmax:.6g}") 120 | print("# SwapXY:", args.swapxy) 121 | print() 122 | print(data_uri) 123 | 124 | if __name__ == "__main__": 125 | main() 126 | -------------------------------------------------------------------------------- /src/brdf/brdfFragment.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUV; 2 | 3 | // PROD MODE 4 | #define NUM_SAMPLES 16384u 5 | // DEV MODE 6 | // #define NUM_SAMPLES 4096u 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef BRDF_V_HEIGHT_CORRELATED 15 | // Correlated 16 | #define visibility(nov, nol, a) smithVisibility_GGXCorrelated(nov, nol, a) 17 | #else 18 | // Uncorrelated 19 | #define visibility(nov, nol, a) smithVisibility_TrowbridgeReitzGGXFast(nov, nol, a) 20 | #endif 21 | 22 | #ifdef MS_BRDF_ENERGY_CONSERVATION 23 | /* 24 | * Fc = (1 - V•H)^5 25 | * F(h) = f0*(1 - Fc) + f90 * Fc 26 | * 27 | * f0 and f90 are known at runtime, but thankfully can be factored out, allowing us 28 | * to split the integral in two terms and store both terms separately in a LUT. 29 | * 30 | * At runtime, we can reconstruct Er() exactly as below: 31 | * 32 | * 4 33 | * DFV.x = --- ∑ Fc V(v, l) ------- 34 | * N h 35 | * 36 | * 37 | * 4 38 | * DFV.y = --- ∑ V(v, l) ------- 39 | * N h 40 | * 41 | * 42 | * Er() = (f90 - f0) * DFV.x + f0 * DFV.y 43 | * Assuming f90 = 1 44 | * 45 | * = mix(DFV.xxx, DFV.yyy, f0) 46 | * 47 | */ 48 | vec2 packResult(float V, float Fc) { 49 | vec2 result; 50 | result.x = V * Fc; 51 | result.y = V; 52 | return result; 53 | } 54 | #else 55 | /* 56 | * Fc = (1 - V•H)^5 57 | * F(h) = f0*(1 - Fc) + f90*Fc 58 | * 59 | * f0 and f90 are known at runtime, but thankfully can be factored out, allowing us 60 | * to split the integral in two terms and store both terms separately in a LUT. 61 | * 62 | * At runtime, we can reconstruct Er() exactly as below: 63 | * 64 | * 4 65 | * DFV.x = --- ∑ (1 - Fc) V(v, l) ------- 66 | * N h 67 | * 68 | * 69 | * 4 70 | * DFV.y = --- ∑ ( Fc) V(v, l) ------- 71 | * N h 72 | * 73 | * 74 | * Er() = f0 * DFV.x + f90 * DFV.y 75 | * 76 | */ 77 | vec2 packResult(float V, float Fc) { 78 | vec2 result; 79 | result.x = (1.0 - Fc) * V; 80 | result.y = Fc * V; 81 | return result; 82 | } 83 | #endif 84 | 85 | /* 86 | * 87 | * Importance sampling GGX - Trowbridge-Reitz 88 | * ------------------------------------------ 89 | * 90 | * Important samples are chosen to integrate Dggx() * cos(theta) over the hemisphere. 91 | * 92 | * All calculations are made in tangent space, with n = [0 0 1] 93 | * 94 | * h (important sample) 95 | * /. 96 | * / . 97 | * / . 98 | * / . 99 | * --------o----+-------> n 100 | * cos(theta) 101 | * = n•h 102 | * 103 | * h is micro facet's normal 104 | * l is the reflection of v around h, l = reflect(-v, h) ==> v•h = l•h 105 | * 106 | * n•v is given as an input parameter at runtime 107 | * 108 | * Since n = [0 0 1], we also have v.z = n•v 109 | * 110 | * Since we need to compute v•h, we chose v as below. This choice only affects the 111 | * computation of v•h (and therefore the fresnel term too), but doesn't affect 112 | * n•l, which only relies on l.z (which itself only relies on v.z, i.e.: n•v) 113 | * 114 | * | sqrt(1 - (n•v)^2) (sin) 115 | * v = | 0 116 | * | n•v (cos) 117 | * 118 | * 119 | * h = important_sample_ggx() 120 | * 121 | * l = reflect(-v, h) = 2 * v•h * h - v; 122 | * 123 | * n•l = [0 0 1] • l = l.z 124 | * 125 | * n•h = [0 0 1] • l = h.z 126 | * 127 | * 128 | * pdf() = D(h) |J(h)| 129 | * 130 | * 1 131 | * |J(h)| = ---------- 132 | * 4 133 | * 134 | * 135 | * Evaluating the integral 136 | * ----------------------- 137 | * 138 | * We are trying to evaluate the following integral: 139 | * 140 | * / 141 | * Er() = | fr(s) ds 142 | * / 143 | * Ω 144 | * 145 | * For this, we're using importance sampling: 146 | * 147 | * 1 fr(h) 148 | * Er() = --- ∑ ------- 149 | * N h pdf 150 | * 151 | * with: 152 | * 153 | * fr() = D(h) F(h) V(v, l) 154 | * 155 | * 156 | * It results that: 157 | * 158 | * 1 4 159 | * Er() = --- ∑ D(h) F(h) V(v, l) ------------ 160 | * N h D(h) 161 | * 162 | * 163 | * +-------------------------------------------+ 164 | * | 4 | 165 | * | Er() = --- ∑ F(h) V(v, l) ------- | 166 | * | N h | 167 | * +-------------------------------------------+ 168 | * 169 | */ 170 | 171 | vec3 DFV(float NdotV, float roughness) { 172 | vec3 result = vec3(0.); 173 | 174 | vec3 V; 175 | V.x = sqrt(1.0 - NdotV*NdotV); 176 | V.y = 0.0; 177 | V.z = NdotV; 178 | 179 | // from perceptual to linear roughness 180 | float alpha = square(roughness); 181 | 182 | for(uint i = 0u; i < NUM_SAMPLES; ++i) 183 | { 184 | vec2 Xi = hammersley(i, NUM_SAMPLES); 185 | 186 | vec3 H = hemisphereImportanceSampleDggx(Xi, alpha); 187 | vec3 L = normalize(2.0 * dot(V, H) * H - V); 188 | 189 | float VdotH = saturate(dot(V, H)); 190 | float NdotL = saturate(L.z); 191 | float NdotH = saturate(H.z); 192 | 193 | if(NdotL > 0.0) 194 | { 195 | float Vis = visibility(NdotV, NdotL, alpha); 196 | float WeightedVis = Vis * (VdotH / NdotH) * NdotL; 197 | float Fc = pow5(1.0 - VdotH); 198 | 199 | result.rg += packResult(WeightedVis, Fc); 200 | } 201 | 202 | #ifdef SHEEN 203 | vec3 HCharlie = hemisphereImportanceSampleDCharlie(Xi, alpha); 204 | vec3 LCharlie = normalize(2.0 * dot(V, HCharlie) * HCharlie - V); 205 | 206 | float VdotHCharlie = saturate(dot(V, HCharlie)); 207 | float NdotLCharlie = saturate(LCharlie.z); 208 | float NdotHCharlie = saturate(HCharlie.z); 209 | 210 | if (NdotLCharlie > 0.0) { 211 | float Vis = visibility_Ashikhmin(NdotV, NdotLCharlie); 212 | float WeightedVis = Vis * (VdotHCharlie / NdotHCharlie) * NdotLCharlie; 213 | 214 | result.b += WeightedVis; 215 | } 216 | #endif 217 | } 218 | 219 | result = result * 4. / float(NUM_SAMPLES); 220 | 221 | return result; 222 | } 223 | 224 | void main() 225 | { 226 | vec3 integratedBRDF = DFV(vUV.x, vUV.y); 227 | 228 | #ifdef RGBD 229 | gl_FragColor = toRGBD(integratedBRDF); 230 | #else 231 | gl_FragColor = vec4(integratedBRDF, 1.); 232 | #endif 233 | } -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Babylon.js Texture Tools 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 146 | 147 | 148 | 149 |
150 | 155 | 160 |
161 | Loading... 162 |
163 |
164 | 165 |
166 | 177 | 195 | 201 |
202 |
203 |
204 | Drag and drop an hdr file here to start processing. 205 |
206 |
207 |
208 |
209 | Drag and drop an hdr file here to start processing. 210 |
211 |
212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /src/ibl/iblSpecularEffect.ts: -------------------------------------------------------------------------------- 1 | import { EffectWrapper, EffectRenderer } from "@babylonjs/core/Materials/effectRenderer"; 2 | import { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 3 | import { BaseTexture } from "@babylonjs/core/Materials/Textures/baseTexture"; 4 | import { Constants } from "@babylonjs/core/Engines/constants"; 5 | import { Tools } from "@babylonjs/core/Misc/tools"; 6 | import { Scalar } from "@babylonjs/core/Maths/math.scalar"; 7 | import { EnvironmentTextureTools } from "@babylonjs/core/Misc/environmentTextureTools"; 8 | import { RenderTargetWrapper } from "@babylonjs/core/Engines/renderTargetWrapper"; 9 | 10 | import "@babylonjs/core/Engines/Extensions/engine.renderTarget"; 11 | import "@babylonjs/core/Engines/Extensions/engine.renderTargetCube"; 12 | 13 | import "@babylonjs/core/Shaders/ShadersInclude/helperFunctions"; 14 | import "@babylonjs/core/Shaders/ShadersInclude/pbrHelperFunctions"; 15 | import "@babylonjs/core/Shaders/ShadersInclude/pbrBRDFFunctions"; 16 | import "@babylonjs/core/Shaders/ShadersInclude/importanceSampling"; 17 | import "@babylonjs/core/Shaders/ShadersInclude/hdrFilteringFunctions"; 18 | 19 | import fragmentShader from "./iblSpecularFragment.glsl"; 20 | import { SphericalPolynomial } from "@babylonjs/core"; 21 | 22 | export class IBLSpecularEffect { 23 | public rtw!: RenderTargetWrapper; 24 | 25 | private readonly _lodGenerationOffset = 0; 26 | private readonly _lodGenerationScale = 0.8; 27 | private readonly _engine: ThinEngine; 28 | private readonly _effectRenderer: EffectRenderer; 29 | 30 | constructor(engine: ThinEngine, effectRenderer: EffectRenderer) { 31 | this._engine = engine; 32 | this._effectRenderer = effectRenderer; 33 | } 34 | 35 | public render(texture: BaseTexture, size: number): void { 36 | this.rtw = this._createRenderTarget(size); 37 | 38 | const effectWrapper = this._getEffect(texture); 39 | 40 | this._effectRenderer.setViewport(); 41 | 42 | const intTexture = texture.getInternalTexture(); 43 | if (intTexture) { 44 | // Just in case generate fresh clean mips. 45 | this._engine.updateTextureSamplingMode(Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, intTexture, true); 46 | } 47 | 48 | this._effectRenderer.applyEffectWrapper(effectWrapper); 49 | 50 | const textureWidth = texture.getSize().width; 51 | const mipmapsCount = Math.round(Scalar.Log2(textureWidth)) + 1; 52 | effectWrapper.effect.setTexture("environmentMap", texture); 53 | effectWrapper.effect.setFloat2("textureInfo", textureWidth, mipmapsCount - 1); 54 | 55 | const mipmapsCountOutput = Math.round(Scalar.Log2(size)) + 1; 56 | for (let face = 0; face < 6; face++) { 57 | for (let lod = 0; lod < mipmapsCountOutput; lod++) { 58 | this._engine.bindFramebuffer(this.rtw, face, undefined, undefined, true, lod); 59 | 60 | this._effectRenderer.applyEffectWrapper(effectWrapper); 61 | 62 | effectWrapper.effect.setFloat("face", face); 63 | 64 | let alpha = Math.pow(2, (lod - this._lodGenerationOffset) / this._lodGenerationScale) / size; 65 | if (lod === 0) { 66 | alpha = 0; 67 | } 68 | 69 | effectWrapper.effect.setFloat("linearRoughness", alpha); 70 | 71 | this._effectRenderer.draw(); 72 | } 73 | } 74 | 75 | this._engine.unBindFramebuffer(this.rtw); 76 | 77 | texture.sphericalPolynomial; 78 | effectWrapper.dispose(); 79 | } 80 | 81 | public save(texture: BaseTexture, size: number): void { 82 | this.render(texture, size); 83 | 84 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 85 | const rtwTexture = this.rtw.texture!; 86 | rtwTexture._sphericalPolynomial = null; 87 | rtwTexture._sphericalPolynomialPromise = null; 88 | rtwTexture._sphericalPolynomialComputed = false; 89 | 90 | const babylonTexture = new BaseTexture(this._engine, rtwTexture); 91 | babylonTexture.lodGenerationOffset = this._lodGenerationOffset; 92 | babylonTexture.lodGenerationScale = this._lodGenerationScale; 93 | babylonTexture.gammaSpace = false; 94 | babylonTexture.forceSphericalPolynomialsRecompute(); 95 | babylonTexture.irradianceTexture = texture.irradianceTexture; 96 | 97 | // Calling into it should trigger the computation. 98 | babylonTexture.sphericalPolynomial; 99 | const polynomialPromise: Promise = babylonTexture.getInternalTexture()?._sphericalPolynomialPromise ?? Promise.resolve(); 100 | 101 | polynomialPromise.then(() => { 102 | EnvironmentTextureTools.CreateEnvTextureAsync(babylonTexture).then((buffer: ArrayBuffer) => { 103 | const blob = new Blob([buffer], { type: "octet/stream" }); 104 | // Remove 'file:' prefix. 105 | const name = texture.name.split(':').pop() || 'environment'; 106 | // Remove file extension. 107 | const fileName = name.split('.').shift(); 108 | Tools.Download(blob, `${fileName}.env`); 109 | }) 110 | .catch((error: unknown) => { 111 | console.error(error); 112 | alert(error); 113 | }); 114 | }) 115 | .catch((error: unknown) => { 116 | console.error(error); 117 | alert(error); 118 | }); 119 | } 120 | 121 | private _getEffect(texture: BaseTexture): EffectWrapper { 122 | const defines = []; 123 | if (texture.gammaSpace) { 124 | defines.push("#define GAMMA_INPUT"); 125 | } 126 | 127 | const shader = defines.join("\n") + "\n" + fragmentShader; 128 | 129 | const effectWrapper = new EffectWrapper({ 130 | engine: this._engine, 131 | name: "IBLSpecular", 132 | fragmentShader: shader, 133 | samplerNames: ["environmentMap"], 134 | uniformNames: ["textureInfo", "face", "linearRoughness"], 135 | }); 136 | 137 | return effectWrapper; 138 | } 139 | 140 | private _createRenderTarget(size: number): RenderTargetWrapper { 141 | if (this.rtw) { 142 | this.rtw.dispose(false); 143 | } 144 | 145 | const rtw = this._engine.createRenderTargetCubeTexture(size, { 146 | format: Constants.TEXTUREFORMAT_RGBA, 147 | type: Constants.TEXTURETYPE_FLOAT, 148 | generateMipMaps: false, 149 | generateDepthBuffer: false, 150 | generateStencilBuffer: false, 151 | samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE, 152 | }); 153 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 154 | const texture = rtw.texture!; 155 | 156 | this._engine.updateTextureWrappingMode(texture, 157 | Constants.TEXTURE_CLAMP_ADDRESSMODE, 158 | Constants.TEXTURE_CLAMP_ADDRESSMODE, 159 | Constants.TEXTURE_CLAMP_ADDRESSMODE); 160 | 161 | this._engine.updateTextureSamplingMode(Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, texture, true); 162 | 163 | return rtw; 164 | } 165 | } -------------------------------------------------------------------------------- /native/LTCGenerator/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace glm; 3 | 4 | #include 5 | #include 6 | 7 | #include "LTC.h" 8 | #include "brdf.h" 9 | #include "brdf_ggx.h" 10 | #include "brdf_beckmann.h" 11 | #include "brdf_disneyDiffuse.h" 12 | 13 | #include "nelder_mead.h" 14 | 15 | #ifdef __EMSCRIPTEN__ 16 | #include 17 | #include 18 | #endif 19 | 20 | #define pi 3.141592653f 21 | 22 | // computes 23 | // * the norm (albedo) of the BRDF 24 | // * the average Schlick Fresnel value 25 | // * the average direction of the BRDF 26 | void computeAvgTerms(const Brdf& brdf, const vec3& V, const float alpha, 27 | float& norm, float& fresnel, vec3& averageDir, int Nsample) 28 | { 29 | norm = 0.0f; 30 | fresnel = 0.0f; 31 | averageDir = vec3(0, 0, 0); 32 | 33 | for (int j = 0; j < Nsample; ++j) 34 | for (int i = 0; i < Nsample; ++i) 35 | { 36 | const float U1 = (i + 0.5f)/Nsample; 37 | const float U2 = (j + 0.5f)/Nsample; 38 | 39 | // sample 40 | const vec3 L = brdf.sample(V, alpha, U1, U2); 41 | 42 | // eval 43 | float pdf; 44 | float eval = brdf.eval(V, L, alpha, pdf); 45 | 46 | if (pdf > 0) 47 | { 48 | float weight = eval / pdf; 49 | 50 | vec3 H = normalize(V+L); 51 | 52 | // accumulate 53 | norm += weight; 54 | fresnel += weight * pow(1.0f - glm::max(dot(V, H), 0.0f), 5.0f); 55 | averageDir += weight * L; 56 | } 57 | } 58 | 59 | norm /= (float)(Nsample*Nsample); 60 | fresnel /= (float)(Nsample*Nsample); 61 | 62 | // clear y component, which should be zero with isotropic BRDFs 63 | averageDir.y = 0.0f; 64 | 65 | averageDir = normalize(averageDir); 66 | } 67 | 68 | // compute the error between the BRDF and the LTC 69 | // using Multiple Importance Sampling 70 | float computeError(const LTC& ltc, const Brdf& brdf, const vec3& V, const float alpha, int Nsample) 71 | { 72 | double error = 0.0; 73 | 74 | for (int j = 0; j < Nsample; ++j) 75 | for (int i = 0; i < Nsample; ++i) 76 | { 77 | const float U1 = (i + 0.5f)/Nsample; 78 | const float U2 = (j + 0.5f)/Nsample; 79 | 80 | // importance sample LTC 81 | { 82 | // sample 83 | const vec3 L = ltc.sample(U1, U2); 84 | 85 | float pdf_brdf; 86 | float eval_brdf = brdf.eval(V, L, alpha, pdf_brdf); 87 | float eval_ltc = ltc.eval(L); 88 | float pdf_ltc = eval_ltc/ltc.magnitude; 89 | 90 | // error with MIS weight 91 | double error_ = fabsf(eval_brdf - eval_ltc); 92 | error_ = error_*error_*error_; 93 | error += error_/(pdf_ltc + pdf_brdf); 94 | } 95 | 96 | // importance sample BRDF 97 | { 98 | // sample 99 | const vec3 L = brdf.sample(V, alpha, U1, U2); 100 | 101 | float pdf_brdf; 102 | float eval_brdf = brdf.eval(V, L, alpha, pdf_brdf); 103 | float eval_ltc = ltc.eval(L); 104 | float pdf_ltc = eval_ltc/ltc.magnitude; 105 | 106 | // error with MIS weight 107 | double error_ = fabsf(eval_brdf - eval_ltc); 108 | error_ = error_*error_*error_; 109 | error += error_/(pdf_ltc + pdf_brdf); 110 | } 111 | } 112 | 113 | return (float)error / (float)(Nsample*Nsample); 114 | } 115 | 116 | struct FitLTC 117 | { 118 | FitLTC(LTC& ltc_, const Brdf& brdf, bool isotropic_, const vec3& V_, float alpha_, int Nsample_) : 119 | ltc(ltc_), brdf(brdf), V(V_), alpha(alpha_), isotropic(isotropic_), Nsample(Nsample_) 120 | { 121 | } 122 | 123 | void update(const float* params) 124 | { 125 | float m11 = std::max(params[0], 1e-7f); 126 | float m22 = std::max(params[1], 1e-7f); 127 | float m13 = params[2]; 128 | 129 | if (isotropic) 130 | { 131 | ltc.m11 = m11; 132 | ltc.m22 = m11; 133 | ltc.m13 = 0.0f; 134 | } 135 | else 136 | { 137 | ltc.m11 = m11; 138 | ltc.m22 = m22; 139 | ltc.m13 = m13; 140 | } 141 | ltc.update(); 142 | } 143 | 144 | float operator()(const float* params) 145 | { 146 | update(params); 147 | return computeError(ltc, brdf, V, alpha, Nsample); 148 | } 149 | 150 | const Brdf& brdf; 151 | LTC& ltc; 152 | bool isotropic; 153 | 154 | const vec3& V; 155 | float alpha; 156 | int Nsample; 157 | }; 158 | 159 | // fit brute force 160 | // refine first guess by exploring parameter space 161 | void fit(LTC& ltc, const Brdf& brdf, const vec3& V, const float alpha, const float epsilon = 0.05f, const bool isotropic = false, int Nsample = 32) 162 | { 163 | float startFit[3] = { ltc.m11, ltc.m22, ltc.m13 }; 164 | float resultFit[3]; 165 | 166 | FitLTC fitter(ltc, brdf, isotropic, V, alpha, Nsample); 167 | 168 | // Find best-fit LTC lobe (scale, alphax, alphay) 169 | float error = NelderMead<3>(resultFit, startFit, epsilon, 1e-5f, 100, fitter); 170 | 171 | // Update LTC with best fitting values 172 | fitter.update(resultFit); 173 | } 174 | 175 | // fit data 176 | void fitTab(mat3* tab, vec2* tabMagFresnel, const int N, const Brdf& brdf, int NSamples, float MIN_ALPHA) 177 | { 178 | LTC ltc; 179 | 180 | // loop over theta and alpha 181 | for (int a = N - 1; a >= 0; --a) 182 | for (int t = 0; t <= N - 1; ++t) 183 | { 184 | // parameterised by sqrt(1 - cos(theta)) 185 | float x = t/float(N - 1); 186 | float ct = 1.0f - x*x; 187 | float theta = std::min(1.57f, acosf(ct)); 188 | const vec3 V = vec3(sinf(theta), 0, cosf(theta)); 189 | 190 | // alpha = roughness^2 191 | float roughness = a/float(N - 1); 192 | float alpha = std::max(roughness*roughness, MIN_ALPHA); 193 | 194 | cout << "a = " << a << "\t t = " << t << endl; 195 | cout << "alpha = " << alpha << "\t theta = " << theta << endl; 196 | cout << endl; 197 | 198 | vec3 averageDir; 199 | computeAvgTerms(brdf, V, alpha, ltc.magnitude, ltc.fresnel, averageDir, NSamples); 200 | 201 | bool isotropic; 202 | 203 | // 1. first guess for the fit 204 | // init the hemisphere in which the distribution is fitted 205 | // if theta == 0 the lobe is rotationally symmetric and aligned with Z = (0 0 1) 206 | if (t == 0) 207 | { 208 | ltc.X = vec3(1, 0, 0); 209 | ltc.Y = vec3(0, 1, 0); 210 | ltc.Z = vec3(0, 0, 1); 211 | 212 | if (a == N - 1) // roughness = 1 213 | { 214 | ltc.m11 = 1.0f; 215 | ltc.m22 = 1.0f; 216 | } 217 | else // init with roughness of previous fit 218 | { 219 | ltc.m11 = tab[a + 1 + t*N][0][0]; 220 | ltc.m22 = tab[a + 1 + t*N][1][1]; 221 | } 222 | 223 | ltc.m13 = 0; 224 | ltc.update(); 225 | 226 | isotropic = true; 227 | } 228 | // otherwise use previous configuration as first guess 229 | else 230 | { 231 | vec3 L = averageDir; 232 | vec3 T1(L.z, 0, -L.x); 233 | vec3 T2(0, 1, 0); 234 | ltc.X = T1; 235 | ltc.Y = T2; 236 | ltc.Z = L; 237 | 238 | ltc.update(); 239 | 240 | isotropic = false; 241 | } 242 | 243 | // 2. fit (explore parameter space and refine first guess) 244 | float epsilon = 0.05f; 245 | fit(ltc, brdf, V, alpha, epsilon, isotropic, NSamples); 246 | 247 | // copy data 248 | tab[a + t*N] = ltc.M; 249 | tabMagFresnel[a + t*N][0] = ltc.magnitude; 250 | tabMagFresnel[a + t*N][1] = ltc.fresnel; 251 | 252 | // kill useless coefs in matrix 253 | tab[a+t*N][0][1] = 0; 254 | tab[a+t*N][1][0] = 0; 255 | tab[a+t*N][2][1] = 0; 256 | tab[a+t*N][1][2] = 0; 257 | 258 | cout << tab[a+t*N][0][0] << "\t " << tab[a+t*N][1][0] << "\t " << tab[a+t*N][2][0] << endl; 259 | cout << tab[a+t*N][0][1] << "\t " << tab[a+t*N][1][1] << "\t " << tab[a+t*N][2][1] << endl; 260 | cout << tab[a+t*N][0][2] << "\t " << tab[a+t*N][1][2] << "\t " << tab[a+t*N][2][2] << endl; 261 | cout << endl; 262 | } 263 | } 264 | 265 | float sqr(float x) 266 | { 267 | return x*x; 268 | } 269 | 270 | float G(float w, float s, float g) 271 | { 272 | return -2.0f*sinf(w)*cosf(s)*cosf(g) + pi/2.0f - g + sinf(g)*cosf(g); 273 | } 274 | 275 | float H(float w, float s, float g) 276 | { 277 | float sinsSq = sqr(sin(s)); 278 | float cosgSq = sqr(cos(g)); 279 | 280 | return cosf(w)*(cosf(g)*sqrtf(sinsSq - cosgSq) + sinsSq*asinf(cosf(g)/sinf(s))); 281 | } 282 | 283 | float ihemi(float w, float s) 284 | { 285 | float g = asinf(cosf(s)/sinf(w)); 286 | float sinsSq = sqr(sinf(s)); 287 | 288 | if (w >= 0.0f && w <= (pi/2.0f - s)) 289 | return pi*cosf(w)*sinsSq; 290 | 291 | if (w >= (pi/2.0f - s) && w < pi/2.0f) 292 | return pi*cosf(w)*sinsSq + G(w, s, g) - H(w, s, g); 293 | 294 | if (w >= pi/2.0f && w < (pi/2.0f + s)) 295 | return G(w, s, g) + H(w, s, g); 296 | 297 | return 0.0f; 298 | } 299 | 300 | void genSphereTab(float* tabSphere, int N) 301 | { 302 | for (int j = 0; j < N; ++j) 303 | for (int i = 0; i < N; ++i) 304 | { 305 | const float U1 = float(i)/(N - 1); 306 | const float U2 = float(j)/(N - 1); 307 | 308 | // z = cos(elevation angle) 309 | float z = 2.0f*U1 - 1.0f; 310 | 311 | // length of average dir., proportional to sin(sigma)^2 312 | float len = U2; 313 | 314 | float sigma = asinf(sqrtf(len)); 315 | float omega = acosf(z); 316 | 317 | // compute projected (cosine-weighted) solid angle of spherical cap 318 | float value = 0.0f; 319 | 320 | if (sigma > 0.0f) 321 | value = ihemi(omega, sigma)/(pi*len); 322 | else 323 | value = std::max(z, 0.0f); 324 | 325 | if (value != value) 326 | printf("nan!\n"); 327 | 328 | tabSphere[i + j*N] = value; 329 | } 330 | } 331 | 332 | void packTab( 333 | vec4* tex1, vec4* tex2, 334 | const mat3* tab, 335 | const vec2* tabMagFresnel, 336 | const float* tabSphere, 337 | int N) 338 | { 339 | for (int i = 0; i < N*N; ++i) 340 | { 341 | const mat3& m = tab[i]; 342 | 343 | mat3 invM = inverse(m); 344 | 345 | // normalize by the middle element 346 | invM /= invM[1][1]; 347 | 348 | // store the variable terms 349 | tex1[i].x = invM[0][0]; 350 | tex1[i].y = invM[0][2]; 351 | tex1[i].z = invM[2][0]; 352 | tex1[i].w = invM[2][2]; 353 | tex2[i].x = tabMagFresnel[i][0]; 354 | tex2[i].y = tabMagFresnel[i][1]; 355 | tex2[i].z = 0.0f; // unused 356 | tex2[i].w = tabSphere[i]; 357 | } 358 | } 359 | 360 | #ifdef __cplusplus 361 | #define EXTERN extern "C" 362 | #else 363 | #define EXTERN 364 | #endif 365 | 366 | #ifdef __EMSCRIPTEN__ 367 | 368 | #include 369 | #include 370 | #include 371 | #include 372 | 373 | // Function to create a memory_view from a vector of floats and return emscripten::val 374 | emscripten::val BuildLTC(int N, int Nsample, float MIN_ALPHA) { 375 | // BRDF to fit 376 | //BrdfGGX brdf; 377 | //BrdfBeckmann brdf; 378 | BrdfDisneyDiffuse brdf; 379 | 380 | // allocate data 381 | mat3* tab = new mat3[N*N]; 382 | vec2* tabMagFresnel = new vec2[N*N]; 383 | float* tabSphere = new float[N*N]; 384 | 385 | // fit 386 | fitTab(tab, tabMagFresnel, N, brdf, Nsample, MIN_ALPHA); 387 | 388 | // projected solid angle of a spherical cap, clipped to the horizon 389 | genSphereTab(tabSphere, N); 390 | 391 | size_t elementCount = N * N; 392 | vec4* ltcMem = new vec4[elementCount * 2]; 393 | vec4* ltc2 = <cMem[0]; 394 | vec4* ltc1 = <cMem[elementCount]; 395 | // pack tables (texture representation) 396 | packTab(ltc1, ltc2, tab, tabMagFresnel, tabSphere, N); 397 | 398 | // delete data 399 | delete[] tab; 400 | delete[] tabMagFresnel; 401 | delete[] tabSphere; 402 | emscripten::memory_view view(elementCount * 8, (float*)ltcMem); 403 | return emscripten::val(view); 404 | } 405 | 406 | // EMSCRIPTEN_BINDINGS macro to expose the function to JavaScript 407 | EMSCRIPTEN_BINDINGS(my_module) { 408 | emscripten::function("BuildLTC", &BuildLTC); 409 | } 410 | #else 411 | 412 | // number of samples used to compute the error during fitting 413 | const int Nsample = 32; 414 | 415 | // minimal roughness (avoid singularities) 416 | const float MIN_ALPHA = 0.00001f; 417 | 418 | const int N = 64; 419 | 420 | int main(int argc, char* argv[]) 421 | { 422 | return 0; 423 | } 424 | 425 | #endif -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { TextureTools } from "./textureTools/textureTools"; 2 | import { BRDFMode } from "./brdf/brdfEffect"; 3 | import { HDRCubeTexture } from "@babylonjs/core/Materials/Textures/hdrCubeTexture"; 4 | import { CubeTexture } from "@babylonjs/core/Materials/Textures/cubeTexture"; 5 | import { FilesInputStore } from "@babylonjs/core/Misc/filesInputStore"; 6 | import { BaseTexture } from "@babylonjs/core/Materials/Textures/baseTexture"; 7 | import { Scene } from "@babylonjs/core/scene"; 8 | 9 | import "@babylonjs/core/Materials/Textures/Loaders/ddsTextureLoader"; 10 | import "@babylonjs/core/Materials/Textures/Loaders/ktxTextureLoader"; 11 | 12 | import("@babylonjs/core/Shaders/hdrIrradianceFiltering.vertex"); 13 | import("@babylonjs/core/Shaders/hdrIrradianceFiltering.fragment"); 14 | import("@babylonjs/core/Shaders/hdrFiltering.vertex"); 15 | import("@babylonjs/core/Shaders/hdrFiltering.fragment"); 16 | 17 | import "./ltc/ltcEffect"; 18 | 19 | 20 | // Custom types 21 | const enum TextureMode { 22 | brdf, 23 | ibl, 24 | iblsh, 25 | ltc 26 | } 27 | 28 | // Find our elements 29 | const mainCanvas = document.getElementById("renderCanvas") as HTMLCanvasElement; 30 | const headerTitle = document.getElementById("headerTitle") as HTMLCanvasElement; 31 | const iblTools = document.getElementById("iblTools") as HTMLCanvasElement; 32 | const iblSHTools = document.getElementById("iblSHTools") as HTMLCanvasElement; 33 | const iblInvite = document.getElementById("iblInvite") as HTMLCanvasElement; 34 | const iblInviteText = document.getElementById("iblInviteText") as HTMLCanvasElement; 35 | const ltcInvite = document.getElementById("ltcInvite") as HTMLCanvasElement; 36 | const ltcInviteText = document.getElementById("ltcInviteText") as HTMLCanvasElement; 37 | const brdfTools = document.getElementById("brdfTools") as HTMLCanvasElement; 38 | const ltcTools = document.getElementById("ltcTools") as HTMLCanvasElement; 39 | 40 | const iblFooter = document.getElementById("iblFooter") as HTMLDivElement; 41 | const correlatedEC = document.getElementById("correlatedEC") as HTMLElement; 42 | const correlated = document.getElementById("correlated") as HTMLElement; 43 | const uncorrelated = document.getElementById("uncorrelated") as HTMLElement; 44 | const toggleSheen = document.getElementById("toggleSheen") as HTMLElement; 45 | const saveBRDF = document.getElementById("saveBRDF") as HTMLElement; 46 | const saveLTC = document.getElementById("saveLTC") as HTMLElement; 47 | 48 | const ltcFooter = document.getElementById("ltcFooter") as HTMLDivElement; 49 | const brdfFooter = document.getElementById("brdfFooter") as HTMLDivElement; 50 | const iblDiffuse = document.getElementById("iblDiffuse") as HTMLElement; 51 | const iblSpecular0 = document.getElementById("iblSpecular0") as HTMLElement; 52 | const iblSpecular1 = document.getElementById("iblSpecular1") as HTMLElement; 53 | const iblSpecular2 = document.getElementById("iblSpecular2") as HTMLElement; 54 | const iblSpecular3 = document.getElementById("iblSpecular3") as HTMLElement; 55 | const iblSpecular4 = document.getElementById("iblSpecular4") as HTMLElement; 56 | const iblSpecular5 = document.getElementById("iblSpecular5") as HTMLElement; 57 | const iblSpecular6 = document.getElementById("iblSpecular6") as HTMLElement; 58 | const iblSpecular64 = document.getElementById("iblSpecular64") as HTMLElement; 59 | const save256 = document.getElementById("save256") as HTMLElement; 60 | 61 | // Texture tools current state. 62 | const textureCanvas = new TextureTools(mainCanvas); 63 | // Keep a dummy scene to avoid procedural textures issues 64 | new Scene(textureCanvas.engine); 65 | 66 | let brdfMode = BRDFMode.CorrelatedGGXEnergieConservation; 67 | let brdfSheen = true; 68 | let cubeTexture: BaseTexture | undefined; 69 | let textureMode = TextureMode.ibl; 70 | 71 | // Switch IBL and BRDF mode 72 | const setMode = (mode: TextureMode): void => { 73 | iblInvite.style.display = "none"; 74 | ltcInvite.style.display = "none"; 75 | 76 | textureMode = mode; 77 | 78 | switch (mode) { 79 | case TextureMode.brdf: 80 | headerTitle.innerText = "BRDF"; 81 | iblFooter.style.display = "none"; 82 | ltcFooter.style.display = "none" 83 | brdfFooter.style.display = "block"; 84 | break; 85 | case TextureMode.ibl: 86 | headerTitle.innerText = "IBL"; 87 | iblFooter.style.display = "block"; 88 | brdfFooter.style.display = "none"; 89 | ltcFooter.style.display = "none"; 90 | iblInvite.style.display = "block"; 91 | iblInviteText.innerText = "Drag and drop an hdr file here to start processing."; 92 | break; 93 | case TextureMode.iblsh: 94 | textureCanvas.clear(); 95 | headerTitle.innerText = "IBL SH"; 96 | iblFooter.style.display = "block"; 97 | brdfFooter.style.display = "none"; 98 | ltcFooter.style.display = "none"; 99 | iblInvite.style.display = "block"; 100 | iblInviteText.innerText = "Drag and drop an hdr file here to start processing."; 101 | break; 102 | case TextureMode.ltc: 103 | textureCanvas.clear(); 104 | headerTitle.innerText = "LTC"; 105 | iblFooter.style.display = "none"; 106 | brdfFooter.style.display = "none"; 107 | ltcFooter.style.display = "block"; 108 | ltcInviteText.innerText = "Click to generate LTC data"; 109 | ltcInvite.style.display = "block"; 110 | break; 111 | } 112 | } 113 | 114 | // Specular IBL generation 115 | const generateSpecularIBL = function(size = 512): void { 116 | if (cubeTexture) { 117 | iblInviteText.innerText = "Processing..."; 118 | setTimeout(() => { 119 | cubeTexture && textureCanvas.saveSpecularIBL(cubeTexture, size); 120 | iblInvite.style.display = "none"; 121 | }, 50); 122 | } 123 | } 124 | 125 | // Blit IBL generation result 126 | const renderSpecularIBL = function(lod: number): void { 127 | textureCanvas.blitSpecularIBL(lod); 128 | } 129 | 130 | const renderLTCData = function() : void { 131 | ltcInviteText.innerText = "Generating LTC texture. See console log for progress."; 132 | 133 | setTimeout(() => { 134 | const ltcData = textureCanvas.renderLTC(); 135 | if (ltcData) { 136 | textureCanvas.saveLTC(ltcData); 137 | } 138 | }, 50); 139 | 140 | 141 | } 142 | 143 | // BRDF generation 144 | const renderBRDF = (): void => { 145 | textureCanvas.renderBRDF(brdfMode, brdfSheen); 146 | }; 147 | 148 | // Initializes 149 | setMode(TextureMode.ibl); 150 | 151 | // User Events 152 | brdfTools.onclick = (): void => { 153 | setMode(TextureMode.brdf); 154 | }; 155 | iblTools.onclick = (): void => { 156 | setMode(TextureMode.ibl); 157 | }; 158 | iblSHTools.onclick = (): void => { 159 | setMode(TextureMode.iblsh); 160 | }; 161 | ltcTools.onclick = (): void => { 162 | setMode(TextureMode.ltc) 163 | } 164 | 165 | correlatedEC.onclick = (): void => { 166 | brdfMode = BRDFMode.CorrelatedGGXEnergieConservation; 167 | renderBRDF(); 168 | }; 169 | correlated.onclick = (): void => { 170 | brdfMode = BRDFMode.CorrelatedGGX; 171 | renderBRDF(); 172 | }; 173 | uncorrelated.onclick = (): void => { 174 | brdfMode = BRDFMode.UncorrelatedGGX; 175 | renderBRDF(); 176 | }; 177 | toggleSheen.onclick = (): void => { 178 | brdfSheen = !brdfSheen; 179 | renderBRDF(); 180 | }; 181 | saveBRDF.onclick = (): void => { 182 | textureCanvas.saveBRDF(brdfMode, brdfSheen); 183 | }; 184 | 185 | saveLTC.onclick = (): void => { 186 | renderLTCData(); 187 | } 188 | 189 | iblDiffuse.onclick = (): void => { 190 | if (cubeTexture) { 191 | textureCanvas.renderDiffuseIBL(cubeTexture); 192 | } 193 | }; 194 | iblSpecular0.onclick = (): void => { 195 | renderSpecularIBL(0); 196 | }; 197 | iblSpecular1.onclick = (): void => { 198 | renderSpecularIBL(1); 199 | }; 200 | iblSpecular2.onclick = (): void => { 201 | renderSpecularIBL(2); 202 | }; 203 | iblSpecular3.onclick = (): void => { 204 | renderSpecularIBL(3); 205 | }; 206 | iblSpecular4.onclick = (): void => { 207 | renderSpecularIBL(4); 208 | }; 209 | iblSpecular5.onclick = (): void => { 210 | renderSpecularIBL(5); 211 | }; 212 | iblSpecular6.onclick = (): void => { 213 | renderSpecularIBL(6); 214 | }; 215 | iblSpecular64.onclick = (): void => { 216 | renderSpecularIBL(6.4); 217 | }; 218 | save256.onclick = (): void => { 219 | generateSpecularIBL(256); 220 | }; 221 | 222 | // File Drag and Drop 223 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 224 | const loadFiles = function(event: any): void { 225 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 226 | let _filesToLoad: any[] | undefined; 227 | 228 | // Handling data transfer via drag'n'drop 229 | if (event && event.dataTransfer && event.dataTransfer.files) { 230 | _filesToLoad = event.dataTransfer.files; 231 | } 232 | 233 | // Handling files from input files 234 | if (event && event.target && event.target.files) { 235 | _filesToLoad = event.target.files; 236 | } 237 | 238 | if (!_filesToLoad || _filesToLoad.length === 0) { 239 | return; 240 | } 241 | 242 | if (_filesToLoad && _filesToLoad.length > 0) { 243 | const files = new Array(); 244 | const items = event.dataTransfer ? event.dataTransfer.items : null; 245 | 246 | for (let i = 0; i < _filesToLoad.length; i++) { 247 | const fileToLoad = _filesToLoad[i]; 248 | 249 | let entry; 250 | if (items) { 251 | const item = items[i]; 252 | if (item.getAsEntry) { 253 | entry = item.getAsEntry(); 254 | } else if (item.webkitGetAsEntry) { 255 | entry = item.webkitGetAsEntry(); 256 | } 257 | } 258 | 259 | if (!entry) { 260 | files.push(fileToLoad); 261 | } else if (!entry.isDirectory) { 262 | files.push(fileToLoad); 263 | } 264 | } 265 | 266 | textureCanvas.engine.clearInternalTexturesCache(); 267 | 268 | for (let i = 0; i < files.length; i++) { 269 | const file = files[i]; 270 | const name = files[i].name.toLowerCase(); 271 | const extension = name.split('.').pop(); 272 | 273 | if (extension === "dds" || extension === "ktx") { 274 | FilesInputStore.FilesToLoad[name] = file; 275 | cubeTexture = new CubeTexture("file:" + name, textureCanvas.engine, null, false, null, () => { 276 | generateSpecularIBL(); 277 | }); 278 | return; 279 | } 280 | else if (extension === "hdr") { 281 | FilesInputStore.FilesToLoad[name] = file; 282 | const generateIrradiance = textureMode === TextureMode.ibl; 283 | 284 | cubeTexture = new HDRCubeTexture("file:" + name, textureCanvas.engine, 512, false, false, false, true, () => { 285 | generateSpecularIBL(); 286 | }, () => { 287 | console.log("Error loading HDR file"); 288 | }, true, generateIrradiance, generateIrradiance); 289 | return; 290 | } 291 | else if (extension === "jpg" || extension === "png") { 292 | const indexPX = name.indexOf("_px"); 293 | if (indexPX > -1) { 294 | for (let j = 0; j < files.length; j++) { 295 | const sbFile = files[j]; 296 | const sbName = files[j].name.toLowerCase(); 297 | FilesInputStore.FilesToLoad[sbName] = sbFile; 298 | } 299 | 300 | const prefix = name.substr(0, indexPX); 301 | cubeTexture = new CubeTexture("file:" + prefix, textureCanvas.engine, null, false, null, () => { 302 | generateSpecularIBL(); 303 | }); 304 | return; 305 | } 306 | } 307 | } 308 | 309 | iblInviteText.innerText = "No Texture to process. Try with an HDR file." 310 | } 311 | } 312 | 313 | const drag = function(e: DragEvent): void { 314 | e.stopPropagation(); 315 | e.preventDefault(); 316 | } 317 | 318 | const drop = function(eventDrop: DragEvent): void { 319 | eventDrop.stopPropagation(); 320 | eventDrop.preventDefault(); 321 | 322 | loadFiles(eventDrop); 323 | } 324 | 325 | const _dragEnterHandler = (e: DragEvent): void => { drag(e); }; 326 | const _dragOverHandler = (e: DragEvent): void => { drag(e); }; 327 | const _dropHandler = (e: DragEvent): void => { drop(e); }; 328 | 329 | mainCanvas.addEventListener("dragenter", _dragEnterHandler, false); 330 | mainCanvas.addEventListener("dragover", _dragOverHandler, false); 331 | mainCanvas.addEventListener("drop", _dropHandler, false); 332 | iblInvite.addEventListener("dragenter", _dragEnterHandler, false); 333 | iblInvite.addEventListener("dragover", _dragOverHandler, false); 334 | iblInvite.addEventListener("drop", _dropHandler, false); 335 | iblInviteText.addEventListener("dragenter", _dragEnterHandler, false); 336 | iblInviteText.addEventListener("dragover", _dragOverHandler, false); 337 | iblInviteText.addEventListener("drop", _dropHandler, false); -------------------------------------------------------------------------------- /www/wasm/LTCGenerator.js: -------------------------------------------------------------------------------- 1 | var Module=typeof Module!="undefined"?Module:{};var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");if(ENVIRONMENT_IS_WORKER){scriptDirectory=nodePath.dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=(filename,binary)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)onerror(err);else onload(binary?data.buffer:data)})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);if(typeof module!="undefined"){module["exports"]=Module}process.on("uncaughtException",ex=>{if(ex!=="unwind"&&!(ex instanceof ExitStatus)&&!(ex.context instanceof ExitStatus)){throw ex}});var nodeMajor=process.versions.node.split(".")[0];if(nodeMajor<15){process.on("unhandledRejection",reason=>{throw reason})}quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow};Module["inspect"]=()=>"[Emscripten Module object]"}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="LTCGenerator.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(binaryFile){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(binaryFile)){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{if(!response["ok"]){throw"failed to load wasm binary file at '"+binaryFile+"'"}return response["arrayBuffer"]()}).catch(()=>getBinary(binaryFile))}else{if(readAsync){return new Promise((resolve,reject)=>{readAsync(binaryFile,response=>resolve(new Uint8Array(response)),reject)})}}}return Promise.resolve().then(()=>getBinary(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>{return WebAssembly.instantiate(binary,imports)}).then(instance=>{return instance}).then(receiver,reason=>{err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}else{return instantiateArrayBuffer(binaryFile,imports,callback)}}function createWasm(){var info={"a":wasmImports};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["y"];updateMemoryViews();wasmTable=Module["asm"]["A"];addOnInit(Module["asm"]["z"]);removeRunDependency("wasm-instantiate");return exports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult);return{}}var tempDouble;var tempI64;function ExitStatus(status){this.name="ExitStatus";this.message=`Program terminated with exit(${status})`;this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast}function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError(`Unknown type size: ${size}`)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return{[name]:function(){return body.apply(this,arguments)}}[name]}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return`${this.name}: ${this.message}`}};return errorClass}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function __embind_register_bool(rawType,name,size,trueValue,falseValue){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":8,"readValueFromPointer":function(pointer){var heap;if(size===1){heap=HEAP8}else if(size===2){heap=HEAP16}else if(size===4){heap=HEAP32}else{throw new TypeError("Unknown boolean type size: "+name)}return this["fromWireType"](heap[pointer>>shift])},destructorFunction:null})}function HandleAllocator(){this.allocated=[undefined];this.freelist=[];this.get=function(id){return this.allocated[id]};this.has=function(id){return this.allocated[id]!==undefined};this.allocate=function(handle){var id=this.freelist.pop()||this.allocated.length;this.allocated[id]=handle;return id};this.free=function(id){this.allocated[id]=undefined;this.freelist.push(id)}}var emval_handles=new HandleAllocator;function __emval_decref(handle){if(handle>=emval_handles.reserved&&0===--emval_handles.get(handle).refcount){emval_handles.free(handle)}}function count_emval_handles(){var count=0;for(var i=emval_handles.reserved;i{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handles.get(handle).value},toHandle:value=>{switch(value){case undefined:return 1;case null:return 2;case true:return 3;case false:return 4;default:{return emval_handles.allocate({refcount:1,value:value})}}}};function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAP32[pointer>>2])}function __embind_register_emval(rawType,name){name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(handle){var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":function(destructors,value){return Emval.toHandle(value)},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function newFunc(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError(`new_ called with constructor type ${typeof constructor} which is not a function`)}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc,isAsync){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns||isAsync?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2])}return array}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr).apply(null,args);return rtn}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}}function embind__requireFunction(signature,rawFunction){signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp}var UnboundTypeError=undefined;function getTypeName(type){var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv}function throwUnboundTypeError(message,types){var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))}function __embind_register_function(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync){var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(heap.buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;i>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder)return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr));var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function __embind_register_std_wstring(rawType,charSize,name){name=readLatin1String(name);var decodeString,encodeString,getHeap,lengthBytesUTF,shift;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;getHeap=()=>HEAPU16;shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=()=>HEAPU32;shift=2}registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function __emval_incref(handle){if(handle>4){emval_handles.get(handle).refcount+=1}}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __emval_take_value(type,arg){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)}function _abort(){abort("")}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function abortOnCannotGrowMemory(requestedSize){abort("OOM")}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;abortOnCannotGrowMemory(requestedSize)}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings}function stringToAscii(str,buffer){for(var i=0;i>0]=str.charCodeAt(i)}HEAP8[buffer>>0]=0}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function initRandomFill(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");var randomFillSync=crypto_module["randomFillSync"];if(randomFillSync){return view=>crypto_module["randomFillSync"](view)}var randomBytes=crypto_module["randomBytes"];return view=>(view.set(randomBytes(view.byteLength)),view)}catch(e){}}abort("initRandomDevice")}function randomFill(view){return(randomFill=initRandomFill())(view)}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{assert(arrayBuffer,`Loading data file "${url}" failed (no arrayBuffer).`);onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw`Loading data file "${url}" failed.`}});if(dep)addRunDependency(dep)}var preloadPlugins=Module["preloadPlugins"]||[];function FS_handledByPreloadPlugin(byteArray,fullname,finish,onerror){if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(function(plugin){if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled}function FS_createPreloadedFile(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish){var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}}function FS_modeStringToFlags(str){var flagModes={"r":0,"r+":2,"w":512|64|1,"w+":512|64|2,"a":1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags}function FS_getMode(canRead,canWrite){var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode}var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:()=>{for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd=-1)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS_modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error(`Invalid encoding type "${opts.encoding}"`)}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.name="ErrnoError";this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();tempI64=[Math.floor(atime/1e3)>>>0,(tempDouble=Math.floor(atime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=atime%1e3*1e3;tempI64=[Math.floor(mtime/1e3)>>>0,(tempDouble=Math.floor(mtime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=mtime%1e3*1e3;tempI64=[Math.floor(ctime/1e3)>>>0,(tempDouble=Math.floor(ctime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];HEAPU32[buf+96>>2]=ctime%1e3*1e3;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+104>>2]=tempI64[0],HEAP32[buf+108>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!=="undefined"){offset+=curr}}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+arraySum(isLeapYear(date.tm_year+1900)?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm,loc){return _strftime(s,maxsize,format,tm)}embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");InternalError=Module["InternalError"]=extendError(Error,"InternalError");init_emval();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();var wasmImports={"k":___cxa_throw,"n":__embind_register_bigint,"i":__embind_register_bool,"w":__embind_register_emval,"e":__embind_register_float,"h":__embind_register_function,"b":__embind_register_integer,"a":__embind_register_memory_view,"d":__embind_register_std_string,"c":__embind_register_std_wstring,"j":__embind_register_void,"l":__emval_decref,"q":__emval_incref,"x":__emval_take_value,"f":_abort,"t":_emscripten_memcpy_big,"s":_emscripten_resize_heap,"p":_environ_get,"r":_environ_sizes_get,"u":_fd_close,"v":_fd_read,"m":_fd_seek,"g":_fd_write,"o":_strftime_l};var asm=createWasm();var ___wasm_call_ctors=function(){return(___wasm_call_ctors=Module["asm"]["z"]).apply(null,arguments)};var ___getTypeName=function(){return(___getTypeName=Module["asm"]["B"]).apply(null,arguments)};var __embind_initialize_bindings=Module["__embind_initialize_bindings"]=function(){return(__embind_initialize_bindings=Module["__embind_initialize_bindings"]=Module["asm"]["C"]).apply(null,arguments)};var ___errno_location=function(){return(___errno_location=Module["asm"]["__errno_location"]).apply(null,arguments)};var _free=function(){return(_free=Module["asm"]["D"]).apply(null,arguments)};var _malloc=function(){return(_malloc=Module["asm"]["E"]).apply(null,arguments)};var ___cxa_is_pointer_type=function(){return(___cxa_is_pointer_type=Module["asm"]["F"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["G"]).apply(null,arguments)};var dynCall_viijii=Module["dynCall_viijii"]=function(){return(dynCall_viijii=Module["dynCall_viijii"]=Module["asm"]["H"]).apply(null,arguments)};var dynCall_iiiiij=Module["dynCall_iiiiij"]=function(){return(dynCall_iiiiij=Module["dynCall_iiiiij"]=Module["asm"]["I"]).apply(null,arguments)};var dynCall_iiiiijj=Module["dynCall_iiiiijj"]=function(){return(dynCall_iiiiijj=Module["dynCall_iiiiijj"]=Module["asm"]["J"]).apply(null,arguments)};var dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=function(){return(dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=Module["asm"]["K"]).apply(null,arguments)};var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); 2 | --------------------------------------------------------------------------------