├── .firebaserc ├── .gitignore ├── LICENSE ├── README.md ├── blueprint.config.js ├── firebase.json ├── package-lock.json ├── package.json ├── src ├── element.css ├── element.examples.js ├── element.ts ├── global.d.ts ├── include.ts ├── index.ts ├── render-delegate.ts └── utils.ts ├── tsconfig.json ├── tsconfig.lib.json ├── usd ├── README.md ├── bell-x1.usdz ├── cassini.usdz ├── ingenuity.usdz └── perseverance.usdz └── wasm ├── README.md ├── emHdBindings.data ├── emHdBindings.js ├── emHdBindings.wasm └── emHdBindings.worker.js /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "sites-fc2cf" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .wireit 2 | .firebase 3 | dist 4 | node_modules 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # Next.js build output 84 | .next 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Cory Rylan 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 | # usd-viewer 2 | 3 | [![npm version](https://img.shields.io/npm/v/usd-viewer?color=%2334D058&label=usd-viewer)](https://www.npmjs.com/package/usd-viewer) 4 | 5 | ### [usd-viewer demo](https://usd-viewer.web.app/usd-viewer-interactive-iframe) 6 | 7 | **Experimental** Web Component for rendering USDZ 3d format. Built on the experimental work and efforts of [github.com/autodesk-forks/USD/tree/release](https://github.com/autodesk-forks/USD/tree/release) 8 | 9 | ## Instalation 10 | 11 | ```shell 12 | npm install usd-viewer 13 | ``` 14 | 15 | ## Web Assembly Dependencies 16 | 17 | Currently this depends on the [SharedArrayBuffer](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) so to enable in the browser the following response headers must be set: 18 | 19 | ```json 20 | "Cross-Origin-Embedder-Policy": "require-corp" 21 | "Cross-Origin-Opener-Policy": "same-origin" 22 | ``` 23 | 24 | To load the Wasm dependencies in the browser copy them from the `node_modules` into your host env. 25 | 26 | ``` 27 | cpy node_modules/usd-viewer/wasm/**/* dist/wasm 28 | ``` 29 | 30 | To change the default path (`./wasm`) of the Wasm resources add the following meta tag to the document. 31 | 32 | ```html 33 | 34 | ``` 35 | 36 | ## Usage 37 | 38 | ```html 39 | 42 | 43 | 44 | ``` 45 | 46 | ## API 47 | 48 | | Property | Attribute | Type | Description | 49 | | ----------------- | ------------------- | ------ | ---------------------------------------------- | 50 | | `src` | `src` | string | source path for usd/usdz file | 51 | | `alt` | `alt` | string | alt descriptive text | 52 | | `controls` | `conrols` | boolean | enable or disable model touch/mouse controls | 53 | | `fileName` | `file-name` | boolean | enable or disable display of file name | 54 | | `autoRotate` | `auto-rotate` | boolean | enable or disable auto rotation of model | 55 | | `autoRotateSpeed` | `auto-rotate-speed` | number | adjust speed of rotations of model | 56 | | `minDistance` | `min-distance` | number | minimum zoom distance of model | 57 | | `maxDistance` | `max-distance` | number | maximum zoom distance of model | 58 | | `zoom` | `zoom` | number | default zoom level of camera relative to model | 59 | 60 | ## Licensing 61 | 62 | The Web Component of this project is MIT licensed, however refer to the following external dependencies and explicitly marked file headers. For additional license details see: 63 | - https://github.com/autodesk-forks/USD 64 | - https://github.com/autodesk-forks/USD/tree/gh-pages/usd_for_web_demos -------------------------------------------------------------------------------- /blueprint.config.js: -------------------------------------------------------------------------------- 1 | 2 | import { resolve } from 'path'; 3 | 4 | export default { 5 | library: { 6 | entryPoints: ['./src/index.ts', './src/include.ts'], 7 | externals: [/^usd-viewer/, /^tslib/, /^lit/, /^three/, /^lit/, /^three-usdz-loader/] 8 | }, 9 | drafter: { 10 | dist: './dist/drafter', 11 | schema: './dist/lib/custom-elements.json', 12 | examples: './src/**/element.examples.js', 13 | baseUrl: './', 14 | commonjs: true, 15 | responseHeaders: { 16 | 'Cross-Origin-Embedder-Policy': 'require-corp', 17 | 'Cross-Origin-Opener-Policy': 'same-origin' 18 | }, 19 | aliases: [ 20 | { find: /^usd-viewer\/(.+)/, replacement: resolve(process.cwd(), './dist/lib/$1') } 21 | ], 22 | head: () => { 23 | return /* html */` 24 | 25 | 26 | 43 | `; 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "site": "usd-viewer", 4 | "public": "dist/drafter", 5 | "cleanUrls": true, 6 | "trailingSlash": false, 7 | "ignore": [ 8 | "firebase.json", 9 | "**/.*", 10 | "**/node_modules/**" 11 | ], 12 | "headers": [ 13 | { 14 | "regex": "/.*", 15 | "headers": [ 16 | { 17 | "key": "Cross-Origin-Embedder-Policy", 18 | "value": "require-corp" 19 | }, 20 | { 21 | "key": "Cross-Origin-Opener-Policy", 22 | "value": "same-origin" 23 | } 24 | ] 25 | }, 26 | { 27 | "source": "/service-worker.js", 28 | "headers": [ 29 | { 30 | "key": "Cache-Control", 31 | "value": "no-cache" 32 | } 33 | ] 34 | }, 35 | { 36 | "source": "**/*.@(usdz|usd)", 37 | "headers": [ 38 | { 39 | "key": "Cache-Control", 40 | "value": "max-age=31536000" 41 | } 42 | ] 43 | }, 44 | { 45 | "source": "**/*.@(eot|otf|ttf|ttc|woff|woff2|font.css)", 46 | "headers": [ 47 | { 48 | "key": "Cache-Control", 49 | "value": "max-age=31536000" 50 | } 51 | ] 52 | }, 53 | { 54 | "source": "**/*.@(jpg|jpeg|gif|png|svg|webp)", 55 | "headers": [ 56 | { 57 | "key": "Cache-Control", 58 | "value": "max-age=31536000" 59 | } 60 | ] 61 | }, 62 | { 63 | "source": "**/*.@(css)", 64 | "headers": [ 65 | { 66 | "key": "Cache-Control", 67 | "value": "max-age=31536000" 68 | } 69 | ] 70 | }, 71 | { 72 | "source": "**/*.@(js)", 73 | "headers": [ 74 | { 75 | "key": "Cache-Control", 76 | "value": "max-age=31536000" 77 | } 78 | ] 79 | } 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "usd-viewer", 3 | "version": "0.0.0", 4 | "main": "./index.js", 5 | "module": "./index.js", 6 | "typings": "./index.d.ts", 7 | "type": "module", 8 | "customElements": "./custom-elements.json", 9 | "homepage": "https://github.com/coryrylan/usd-viewer", 10 | "keywords": [ 11 | "web components", 12 | "usd", 13 | "3d model" 14 | ], 15 | "license": "MIT", 16 | "description": "experimental usd-viewer web component", 17 | "scripts": { 18 | "ci": "wireit", 19 | "start": "wireit", 20 | "drafter": "wireit", 21 | "drafter:watch": "wireit", 22 | "build": "wireit", 23 | "build:watch": "wireit", 24 | "deploy": "wireit", 25 | "clean": "git clean -dfX" 26 | }, 27 | "wireit": { 28 | "ci": { 29 | "dependencies": [ 30 | "build", 31 | "drafter" 32 | ] 33 | }, 34 | "start": { 35 | "dependencies": [ 36 | "build:watch", 37 | "drafter:watch" 38 | ] 39 | }, 40 | "build": { 41 | "command": "bp build && cpy \"wasm/**/*\" dist/lib/wasm", 42 | "output": ["dist/lib"], 43 | "files": [ 44 | "src/**/*.ts", 45 | "src/**/*.css", 46 | "wasm/**/*", 47 | "blueprint.config.js", 48 | "readme.md", 49 | "package.json", 50 | "tsconfig.json", 51 | "tsconfig.lib.json" 52 | ] 53 | }, 54 | "build:watch": { 55 | "command": "bp build --watch", 56 | "dependencies": [ 57 | "build" 58 | ], 59 | "service": true 60 | }, 61 | "drafter": { 62 | "command": "drafter build && cpy \"dist/lib/wasm/**/*\" dist/drafter/wasm && cpy \"usd/**/*\" dist/drafter/usd", 63 | "output": ["dist/drafter"], 64 | "dependencies": [ 65 | "build" 66 | ], 67 | "files": [ 68 | "dist/wasm/**/*", 69 | "usd/**/*", 70 | "src/**/*.examples.js", 71 | "wasm/**/*", 72 | "usd/**/*", 73 | "blueprint.config.js", 74 | "package.json" 75 | ] 76 | }, 77 | "drafter:watch": { 78 | "command": "drafter build --watch", 79 | "dependencies": [ 80 | "drafter" 81 | ], 82 | "service": true 83 | }, 84 | "deploy": { 85 | "command": "firebase deploy --only hosting:usd-viewer" 86 | } 87 | }, 88 | "files": [ 89 | "*" 90 | ], 91 | "sideEffects": [ 92 | "./include.js" 93 | ], 94 | "exports": { 95 | "./custom-elements.json": "./custom-elements.json", 96 | ".": "./index.js", 97 | "./include/*": "./include/*", 98 | "./*": "./*/index.js" 99 | }, 100 | "author": { 101 | "name": "" 102 | }, 103 | "repository": { 104 | "type": "git", 105 | "url": "https://github.com/coryrylan/usd-viewer" 106 | }, 107 | "dependencies": { 108 | "lit": "^2.6.1", 109 | "three": "^0.149.0" 110 | }, 111 | "devDependencies": { 112 | "@blueprintui/cli": "^0.1.1", 113 | "@blueprintui/drafter": "^0.8.1", 114 | "@rollup/plugin-commonjs": "^24.0.1", 115 | "@types/three": "^0.149.0", 116 | "cpy-cli": "^4.2.0", 117 | "typescript": "~4.7.4", 118 | "wireit": "^0.9.5" 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, *:before, *:after { 6 | box-sizing: inherit; 7 | } 8 | 9 | :host { 10 | display: block; 11 | position: relative; 12 | width: 100%; 13 | height: 100%; 14 | font-family: inherit; 15 | } 16 | 17 | [part='filename'] { 18 | background: ButtonFace; 19 | opacity: 0.8; 20 | color: inherit; 21 | position: absolute; 22 | bottom: 12px; 23 | left: 50%; 24 | padding: 4px 8px; 25 | font-size: 16px; 26 | border-radius: 4px; 27 | transform: translateX(-50%); 28 | width: max-content; 29 | } -------------------------------------------------------------------------------- /src/element.examples.js: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | name: 'usd-viewer', 3 | elements: ['usd-viewer'] 4 | }; 5 | 6 | export function example() { 7 | return /* html */` 8 | 11 | 12 | 13 | `; 14 | } 15 | 16 | export function interactive() { 17 | return /* html */` 18 | 19 | 120 | 121 | 122 |
123 |
124 | 125 |
126 |

usd-viewer

127 | Github 128 | NPM 129 |
130 | 131 | 140 | 141 | 144 | 145 | 148 | 149 | 152 | 153 | 156 | 157 | 160 | 161 | 164 | 165 | 168 |
169 | 170 |
171 | 172 | 188 | `; 189 | } 190 | 191 | export function mutli() { 192 | return /* html */` 193 | 196 | 209 | 210 | 211 | 212 | 213 | 214 | `; 215 | } 216 | -------------------------------------------------------------------------------- /src/element.ts: -------------------------------------------------------------------------------- 1 | import { Scene, Group, Vector3, Box3, PerspectiveCamera, WebGLRenderer, CineonToneMapping, VSMShadowMap, PMREMGenerator } from 'three'; 2 | import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js'; 3 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; 4 | import { LitElement, html, PropertyValues } from 'lit'; 5 | import { state } from 'lit/decorators/state.js'; 6 | import { property } from 'lit/decorators/property.js'; 7 | import { RenderDelegateInterface } from './render-delegate.js'; 8 | import { loadWasmUSD, fetchArrayBuffer } from './utils.js'; 9 | import styles from './element.css' assert { type: 'css' }; 10 | 11 | const USD = await loadWasmUSD(document.querySelector('meta[name="usd-viewer:wasm"]')?.content ?? 'wasm'); 12 | 13 | /** 14 | * @element usd-viewer 15 | */ 16 | export class USDViewer extends LitElement { 17 | @property({ type: String }) src = ''; 18 | 19 | @property({ type: String }) alt = ''; 20 | 21 | @property({ type: Boolean }) controls = true; 22 | 23 | @property({ type: Boolean, attribute: 'file-name' }) fileName: boolean; 24 | 25 | @property({ type: Boolean, attribute: 'auto-rotate' }) autoRotate: boolean; 26 | 27 | @property({ type: Number, attribute: 'auto-rotate-speed' }) autoRotateSpeed = 2; 28 | 29 | @property({ type: Number, attribute: 'min-distance' }) minDistance = 1; 30 | 31 | @property({ type: Number, attribute: 'max-distance' }) maxDistance = 2; 32 | 33 | @property({ type: Number }) zoom = 1; 34 | 35 | @state() private error: string | null = null; 36 | 37 | #DOMRect!: DOMRect; 38 | #scene!: Scene; 39 | #camera!: PerspectiveCamera; 40 | #renderer!: WebGLRenderer; 41 | #controls!: OrbitControls; 42 | #group: Group; 43 | #loadedFile: string; 44 | #loading = false; 45 | #internals = this.attachInternals(); 46 | 47 | static styles = [styles]; 48 | 49 | render() { 50 | return html` 51 | ${this.error ? html`error loading file` : ''} 52 | ${this.fileName ? html`
${this.src.split('/')[this.src.split('/').length - 1]}
` : ''} 53 | ` 54 | } 55 | 56 | async firstUpdated(props: PropertyValues) { 57 | super.firstUpdated(props); 58 | await this.updateComplete; 59 | (this.#internals as any).role = 'img'; 60 | this.#initializeDOMRect(); 61 | this.#intializeCamera(); 62 | this.#intitializeRender(); 63 | this.#intializeScene(); 64 | this.#intializeControls(); 65 | await this.#loadFile(); 66 | this.#updateControls(); 67 | this.#updateCamera(); 68 | this.#animate(); 69 | this.#initializeResizer(); 70 | (this.shadowRoot as ShadowRoot).appendChild(this.#renderer.domElement); 71 | } 72 | 73 | async updated(props: PropertyValues) { 74 | super.updated(props); 75 | await this.updateComplete; 76 | this.#internals.ariaLabel = this.alt; 77 | 78 | if (props.has('src') && this.src !== props.get('src')) { 79 | await this.#loadFile(); 80 | } 81 | this.#updateControls(); 82 | this.#updateCamera(); 83 | } 84 | 85 | #initializeDOMRect() { 86 | this.#DOMRect = this.getBoundingClientRect(); 87 | } 88 | 89 | #intializeScene() { 90 | this.#scene = new Scene(); 91 | this.#scene.environment = new PMREMGenerator(this.#renderer).fromScene(new RoomEnvironment()).texture; 92 | } 93 | 94 | #intializeCamera() { 95 | this.#camera = new PerspectiveCamera(27, this.#DOMRect.width / this.#DOMRect.height, 1, 3500); 96 | } 97 | 98 | #updateCamera() { 99 | const box = new Box3().setFromObject(this.#group); 100 | const size = box.getSize(new Vector3()).length(); 101 | const center = box.getCenter(new Vector3()); 102 | 103 | this.#group.position.x = (this.#group.position.x - center.x); 104 | this.#group.position.y = (this.#group.position.y - center.y); 105 | this.#group.position.z = (this.#group.position.z - center.z); 106 | 107 | this.#camera.near = size / 100; 108 | this.#camera.far = size * 100; 109 | this.#camera.position.copy(center); 110 | this.#camera.position.z = 4; 111 | this.#camera.position.y = 3; 112 | this.#camera.position.x = 5; 113 | this.#camera.zoom = this.zoom; 114 | this.#camera.aspect = this.#DOMRect.width / this.#DOMRect.height; 115 | this.#camera.lookAt(center); 116 | this.#renderer.setSize(this.#DOMRect.width, this.#DOMRect.height); 117 | this.#camera.updateProjectionMatrix(); 118 | } 119 | 120 | #intitializeRender() { 121 | this.#renderer = new WebGLRenderer({ antialias: true, alpha: true }); 122 | this.#renderer.setPixelRatio(this.#DOMRect.width / this.#DOMRect.height); 123 | this.#renderer.setSize(this.#DOMRect.width, this.#DOMRect.height); 124 | this.#renderer.toneMapping = CineonToneMapping; 125 | this.#renderer.toneMappingExposure = 2; 126 | this.#renderer.shadowMap.enabled = false; 127 | this.#renderer.shadowMap.type = VSMShadowMap; 128 | } 129 | 130 | #intializeControls() { 131 | this.#controls = new OrbitControls(this.#camera, this.#renderer.domElement); 132 | this.#controls.update(); 133 | } 134 | 135 | #updateControls() { 136 | const box = new Box3().setFromObject(this.#group); 137 | const size = box.getSize(new Vector3()).length(); 138 | this.#controls.reset(); 139 | this.#controls.autoRotate = this.autoRotate; 140 | this.#controls.autoRotateSpeed = this.autoRotateSpeed; 141 | this.#controls.maxDistance = size * this.maxDistance; 142 | this.#controls.minDistance = size * this.minDistance; 143 | this.#controls.update(); 144 | } 145 | 146 | async #animate() { 147 | this.#renderer.render(this.#scene, this.#camera); 148 | if (this.controls) { 149 | this.#controls.update(); 150 | } 151 | requestAnimationFrame(() => this.#animate()); 152 | } 153 | 154 | async #loadFile() { 155 | if (this.#loading) { 156 | return; 157 | } 158 | 159 | this.#loading = true; 160 | this.error = null; 161 | this.#scene.remove(this.#group); 162 | this.#group = new Group(); 163 | this.#scene.add(this.#group); 164 | 165 | try { 166 | USD.FS.createDataFile('/', this.src, await fetchArrayBuffer(this.src), true, true, true); 167 | 168 | if (this.#loadedFile) { 169 | USD.FS.unlink(this.#loadedFile, true); 170 | } 171 | 172 | this.#loadedFile = this.src; 173 | new RenderDelegateInterface(this.#loadedFile, USD, this.#group).driver.Draw(); 174 | } catch (e) { 175 | this.error = e; 176 | this.#loading = false; 177 | console.error(`error loading model: ${e}`); 178 | return; 179 | } 180 | 181 | this.#loading = false; 182 | } 183 | 184 | #initializeResizer() { 185 | new ResizeObserver(() => this.#resize()).observe(this); 186 | } 187 | 188 | #resize() { 189 | this.#DOMRect = this.getBoundingClientRect(); 190 | this.#camera.aspect = this.#DOMRect.width / this.#DOMRect.height; 191 | this.#camera.updateProjectionMatrix(); 192 | this.#renderer.setSize(this.#DOMRect.width, this.#DOMRect.height); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css' { 2 | const value: CSSStyleSheet; 3 | export default value; 4 | } 5 | -------------------------------------------------------------------------------- /src/include.ts: -------------------------------------------------------------------------------- 1 | import { USDViewer } from './element.js'; 2 | 3 | customElements.get('usd-viewer') || customElements.define('usd-viewer', USDViewer); 4 | 5 | declare global { 6 | interface HTMLElementTagNameMap { 7 | 'usd-viewer': USDViewer; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './element.js'; -------------------------------------------------------------------------------- /src/render-delegate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Modified from original source https://github.com/autodesk-forks/USD/blob/gh-pages/usd_for_web_demos/ThreeJsRenderDelegate.js 3 | */ 4 | 5 | import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, Mesh, MeshPhysicalMaterial, RepeatWrapping, RGBAFormat, TextureLoader } from 'three'; 6 | 7 | declare global { 8 | interface Window { envMap: any; usdRoot: any; driver: any; } 9 | } 10 | 11 | class TextureRegistry { 12 | basename: any; 13 | textures: any; 14 | loader: any; 15 | 16 | constructor(basename: string, private driver: any) { 17 | this.basename = basename; 18 | this.textures = []; 19 | this.loader = new TextureLoader(); 20 | } 21 | getTexture(filename: string) { 22 | if (this.textures[filename]) { 23 | return this.textures[filename]; 24 | } 25 | 26 | let textureResolve: any, textureReject: any; 27 | this.textures[filename] = new Promise((resolve, reject) => { 28 | textureResolve = resolve; 29 | textureReject = reject; 30 | }); 31 | 32 | let resourcePath = filename; 33 | if (filename[0] !== '/') { 34 | resourcePath = this.basename + '[' + filename +']'; 35 | } 36 | 37 | let filetype: string; 38 | if (filename.indexOf('.png') >= filename.length - 5) { 39 | filetype = 'image/png'; 40 | } else if (filename.indexOf('.jpg') >= filename.length - 5) { 41 | filetype = 'image/jpeg'; 42 | } else if (filename.indexOf('.jpeg') >= filename.length - 5) { 43 | filetype = 'image/jpeg'; 44 | } else { 45 | throw new Error('Unknown filetype'); 46 | } 47 | 48 | this.driver.getFile(resourcePath, (loadedFile: any) => { 49 | if (!loadedFile) { 50 | textureReject(new Error('Unknown file: ' + resourcePath)); 51 | return; 52 | } 53 | 54 | let blob = new Blob([loadedFile.slice(0)], {type: filetype}); 55 | let blobUrl = URL.createObjectURL(blob); 56 | 57 | this.loader.load( 58 | blobUrl, 59 | (texture: any) => { 60 | textureResolve(texture); 61 | }, 62 | undefined, 63 | (err: any) => { 64 | textureReject(err); 65 | } 66 | ); 67 | }); 68 | 69 | return this.textures[filename]; 70 | } 71 | } 72 | 73 | class HydraMesh { 74 | _geometry: BufferGeometry; 75 | _id: any; 76 | _interface: any; 77 | _points: any; 78 | _normals: any; 79 | _colors: any; 80 | _uvs: any; 81 | _indices: any; 82 | _mesh: Mesh; 83 | 84 | constructor(id: any, hydraInterface: any, usdRoot: any) { 85 | this._geometry = new BufferGeometry(); 86 | this._id = id; 87 | this._interface = hydraInterface; 88 | this._points = undefined; 89 | this._normals = undefined; 90 | this._colors = undefined; 91 | this._uvs = undefined; 92 | this._indices = undefined; 93 | 94 | const material = new MeshPhysicalMaterial( { 95 | side: DoubleSide, 96 | color: new Color(0x00ff00) // a green color to indicate a missing material 97 | } ); 98 | 99 | this._mesh = new Mesh( this._geometry, material ); 100 | this._mesh.castShadow = true; 101 | this._mesh.receiveShadow = true; 102 | 103 | usdRoot.add(this._mesh); // FIXME 104 | } 105 | 106 | updateOrder(attribute: any, attributeName: any, dimension = 3) { 107 | if (attribute && this._indices) { 108 | let values: any = []; 109 | for (let i = 0; i < this._indices.length; i++) { 110 | let index = this._indices[i] 111 | for (let j = 0; j < dimension; ++j) { 112 | values.push(attribute[dimension * index + j] as never); 113 | } 114 | } 115 | this._geometry.setAttribute( attributeName, new Float32BufferAttribute( values, dimension ) ); 116 | } 117 | } 118 | 119 | updateIndices(indices: any) { 120 | this._indices = []; 121 | for (let i = 0; i< indices.length; i++) { 122 | this._indices.push(indices[i]); 123 | } 124 | //this._geometry.setIndex( indicesArray ); 125 | this.updateOrder(this._points, 'position'); 126 | this.updateOrder(this._normals, 'normal'); 127 | if (this._colors) { 128 | this.updateOrder(this._colors, 'color'); 129 | } 130 | if (this._uvs) { 131 | this.updateOrder(this._uvs, 'uv', 2); 132 | this._geometry.attributes.uv2 = this._geometry.attributes.uv; 133 | } 134 | } 135 | 136 | setTransform(matrix: any) { 137 | (this._mesh.matrix as any).set(...matrix); 138 | this._mesh.matrix.transpose(); 139 | this._mesh.matrixAutoUpdate = false; 140 | } 141 | 142 | updateNormals(normals: any) { 143 | this._normals = normals.slice(0); 144 | this.updateOrder(this._normals, 'normal'); 145 | } 146 | 147 | // This is always called before prims are updated 148 | setMaterial(materialId: any) { 149 | // console.log('Material: ' + materialId); 150 | if (this._interface.materials[materialId]) { 151 | this._mesh.material = this._interface.materials[materialId]._material; 152 | } 153 | } 154 | 155 | setDisplayColor(data: any, interpolation: any) { 156 | let wasDefaultMaterial = false; 157 | if (this._mesh.material === defaultMaterial) { 158 | this._mesh.material = (this._mesh.material as any).clone(); 159 | wasDefaultMaterial = true; 160 | } 161 | 162 | this._colors = null; 163 | 164 | if (interpolation === 'constant') { 165 | (this._mesh.material as any).color = new Color().fromArray(data); 166 | } else if (interpolation === 'vertex') { 167 | // Per-vertex buffer attribute 168 | (this._mesh.material as any).vertexColors = true; 169 | if (wasDefaultMaterial) { 170 | // Reset the pink debugging color 171 | (this._mesh.material as any).color = new Color(0xffffff); 172 | } 173 | this._colors = data.slice(0); 174 | this.updateOrder(this._colors, 'color'); 175 | } else { 176 | console.warn(`Unsupported displayColor interpolation type '${interpolation}'.`); 177 | } 178 | } 179 | 180 | setUV(data: any, dimension: any, interpolation: any) { 181 | // TODO: Support multiple UVs. For now, we simply set uv = uv2, which is required when a material has an aoMap. 182 | this._uvs = null; 183 | 184 | if (interpolation === 'facevarying') { 185 | // The UV buffer has already been prepared on the C++ side, so we just set it 186 | this._geometry.setAttribute('uv', new Float32BufferAttribute(data, dimension)); 187 | } else if (interpolation === 'vertex') { 188 | // We have per-vertex UVs, so we need to sort them accordingly 189 | this._uvs = data.slice(0); 190 | this.updateOrder(this._uvs, 'uv', 2); 191 | } 192 | this._geometry.attributes.uv2 = this._geometry.attributes.uv; 193 | } 194 | 195 | updatePrimvar(name: any, data: any, dimension: any, interpolation: any) { 196 | if (name === 'points' || name === 'normals') { 197 | // Points and normals are set separately 198 | return; 199 | } 200 | 201 | // console.log('Setting PrimVar: ' + name); 202 | 203 | // TODO: Support multiple UVs. For now, we simply set uv = uv2, which is required when a material has an aoMap. 204 | if (name.startsWith('st')) { 205 | name = 'uv'; 206 | } 207 | 208 | switch(name) { 209 | case 'displayColor': 210 | this.setDisplayColor(data, interpolation); 211 | break; 212 | case 'uv': 213 | this.setUV(data, dimension, interpolation); 214 | break; 215 | default: 216 | console.warn('Unsupported primvar', name); 217 | } 218 | } 219 | 220 | updatePoints(points: any) { 221 | this._points = points.slice(0); 222 | this.updateOrder(this._points, 'position'); 223 | } 224 | 225 | commit() { 226 | // Nothing to do here. All Three.js resources are already updated during the sync phase. 227 | } 228 | 229 | } 230 | 231 | let defaultMaterial: any; 232 | 233 | class HydraMaterial { 234 | // Maps USD preview material texture names to Three.js MeshPhysicalMaterial names 235 | static usdPreviewToMeshPhysicalTextureMap = { 236 | 'diffuseColor': 'map', 237 | 'clearcoat': 'clearcoatMap', 238 | 'clearcoatRoughness': 'clearcoatRoughnessMap', 239 | 'emissiveColor': 'emissiveMap', 240 | 'occlusion': 'aoMap', 241 | 'roughness': 'roughnessMap', 242 | 'metallic': 'metalnessMap', 243 | 'normal': 'normalMap', 244 | 'opacity': 'alphaMap' 245 | }; 246 | 247 | static channelMap = { 248 | // Three.js expects many 8bit values such as roughness or metallness in a specific RGB texture channel. 249 | // We could write code to combine multiple 8bit texture files into different channels of one RGB texture where it 250 | // makes sense, but that would complicate this loader a lot. Most Three.js loaders don't seem to do it either. 251 | // Instead, we simply provide the 8bit image as an RGB texture, even though this might be less efficient. 252 | 'r': RGBAFormat, 253 | 'rgb': RGBAFormat, 254 | 'rgba': RGBAFormat 255 | }; 256 | 257 | // Maps USD preview material property names to Three.js MeshPhysicalMaterial names 258 | static usdPreviewToMeshPhysicalMap = { 259 | 'clearcoat': 'clearcoat', 260 | 'clearcoatRoughness': 'clearcoatRoughness', 261 | 'diffuseColor': 'color', 262 | 'emissiveColor': 'emissive', 263 | 'ior': 'ior', 264 | 'metallic': 'metalness', 265 | 'opacity': 'opacity', 266 | 'roughness': 'roughness', 267 | }; 268 | 269 | _id: any; 270 | _nodes: any; 271 | _interface: any; 272 | _material: any; 273 | 274 | constructor(id: any, hydraInterface: any) { 275 | this._id = id; 276 | this._nodes = {}; 277 | this._interface = hydraInterface; 278 | if (!defaultMaterial) { 279 | defaultMaterial = new MeshPhysicalMaterial({ 280 | side: DoubleSide, 281 | color: new Color(0xff2997), // a bright pink color to indicate a missing material 282 | envMap: window.envMap, 283 | }); 284 | } 285 | this._material = defaultMaterial; 286 | } 287 | 288 | updateNode(_networkId: any, path: any, parameters: any) { 289 | // console.log('Updating Material Node: ' + networkId + ' ' + path); 290 | this._nodes[path] = parameters; 291 | } 292 | 293 | assignTexture(mainMaterial: any, parameterName: any) { 294 | const materialParameterMapName = (HydraMaterial as any).usdPreviewToMeshPhysicalTextureMap[parameterName]; 295 | if (materialParameterMapName === undefined) { 296 | console.warn(`Unsupported material texture parameter '${parameterName}'.`); 297 | return; 298 | } 299 | if (mainMaterial[parameterName] && mainMaterial[parameterName].nodeIn) { 300 | const textureFileName = mainMaterial[parameterName].nodeIn.file; 301 | const channel = mainMaterial[parameterName].inputName; 302 | 303 | // For debugging 304 | // const matName = Object.keys(this._nodes).find(key => this._nodes[key] === mainMaterial); 305 | // console.log(`Setting texture '${materialParameterMapName}' (${textureFileName}) of material '${matName}'...`); 306 | 307 | this._interface.registry.getTexture(textureFileName).then((texture: any) => { 308 | if (materialParameterMapName === 'alphaMap') { 309 | // If this is an opacity map, check if it's using the alpha channel of the diffuse map. 310 | // If so, simply change the format of that diffuse map to RGBA and make the material transparent. 311 | // If not, we need to copy the alpha channel into a new texture's green channel, because that's what Three.js 312 | // expects for alpha maps (not supported at the moment). 313 | // NOTE that this only works if diffuse maps are always set before opacity maps, so the order of 314 | // 'assingTexture' calls for a material matters. 315 | if (textureFileName === mainMaterial.diffuseColor?.nodeIn?.file && channel === 'a') { 316 | this._material.map.format = RGBAFormat; 317 | } else { 318 | // TODO: Extract the alpha channel into a new RGB texture. 319 | } 320 | 321 | this._material.transparent = true; 322 | this._material.needsUpdate = true; 323 | return; 324 | } else if (materialParameterMapName === 'metalnessMap') { 325 | this._material.metalness = 1.0; 326 | } else if (materialParameterMapName === 'emissiveMap') { 327 | this._material.emissive = new Color(0xffffff); 328 | } else if (!(HydraMaterial as any).channelMap[channel]) { 329 | console.warn(`Unsupported texture channel '${channel}'!`); 330 | return; 331 | } 332 | 333 | // Clone texture and set the correct format. 334 | const clonedTexture = texture.clone(); 335 | clonedTexture.format = (HydraMaterial as any).channelMap[channel]; 336 | clonedTexture.needsUpdate = true; 337 | clonedTexture.wrapS = RepeatWrapping; 338 | clonedTexture.wrapT = RepeatWrapping; 339 | 340 | this._material[materialParameterMapName] = clonedTexture; 341 | 342 | this._material.needsUpdate = true; 343 | }); 344 | } else { 345 | this._material[materialParameterMapName] = undefined; 346 | } 347 | } 348 | 349 | assignProperty(mainMaterial: any, parameterName: any) { 350 | const materialParameterName = (HydraMaterial as any).usdPreviewToMeshPhysicalMap[parameterName]; 351 | if (materialParameterName === undefined) { 352 | console.warn(`Unsupported material parameter '${parameterName}'.`); 353 | return; 354 | } 355 | if (mainMaterial[parameterName] !== undefined && !mainMaterial[parameterName].nodeIn) { 356 | // console.log(`Assigning property ${parameterName}: ${mainMaterial[parameterName]}`); 357 | if (Array.isArray(mainMaterial[parameterName])) { 358 | this._material[materialParameterName] = new Color().fromArray(mainMaterial[parameterName]); 359 | } else { 360 | this._material[materialParameterName] = mainMaterial[parameterName]; 361 | if (materialParameterName === 'opacity' && mainMaterial[parameterName] < 1.0) { 362 | this._material.transparent = true; 363 | } 364 | } 365 | } 366 | } 367 | 368 | updateFinished(_type: any, relationships: any) { 369 | for (let relationship of relationships) { 370 | relationship.nodeIn = this._nodes[relationship.inputId]; 371 | relationship.nodeOut = this._nodes[relationship.outputId]; 372 | relationship.nodeIn[relationship.inputName] = relationship; 373 | relationship.nodeOut[relationship.outputName] = relationship; 374 | } 375 | // console.log('Finalizing Material: ' + this._id); 376 | 377 | // find the main material node 378 | let mainMaterialNode = undefined; 379 | for (let node of Object.values(this._nodes) as any) { 380 | if (node.diffuseColor) { 381 | mainMaterialNode = node; 382 | break; 383 | } 384 | } 385 | 386 | if (!mainMaterialNode) { 387 | this._material = defaultMaterial; 388 | return; 389 | } 390 | 391 | // TODO: Ideally, we don't recreate the material on every update. 392 | // Creating a new one requires to also update any meshes that reference it. So we're relying on the C++ side to 393 | // call this before also calling `setMaterial` on the affected meshes. 394 | // console.log('Creating Material: ' + this._id); 395 | this._material = new MeshPhysicalMaterial({}); 396 | 397 | // Assign textures 398 | for (let key in HydraMaterial.usdPreviewToMeshPhysicalTextureMap) { 399 | this.assignTexture(mainMaterialNode, key); 400 | } 401 | 402 | // Assign material properties 403 | for (let key in HydraMaterial.usdPreviewToMeshPhysicalMap) { 404 | this.assignProperty(mainMaterialNode, key); 405 | } 406 | 407 | if (window.envMap) { 408 | this._material.envMap = window.envMap; 409 | } 410 | 411 | // console.log(this._material); 412 | } 413 | } 414 | 415 | export class RenderDelegateInterface { 416 | meshes: any; 417 | registry: TextureRegistry; 418 | materials: any; 419 | driver: any; 420 | 421 | constructor(filename: any, Usd: any, private usdRoot: any) { 422 | this.materials = {}; 423 | this.meshes = {}; 424 | this.driver = new Usd.HdWebSyncDriver(this, filename); 425 | this.registry = new TextureRegistry(filename, this.driver); 426 | } 427 | 428 | createRPrim(_typeId: any, id: any) { 429 | // console.log('Creating RPrim: ' + typeId + ' ' + id); 430 | let mesh = new HydraMesh(id, this, this.usdRoot); 431 | this.meshes[id] = mesh; 432 | return mesh; 433 | } 434 | 435 | createBPrim(_typeId: any, _id: any) { 436 | // console.log('Creating BPrim: ' + typeId + ' ' + id); 437 | /*let mesh = new HydraMesh(id, this); 438 | this.meshes[id] = mesh; 439 | return mesh;*/ 440 | } 441 | 442 | createSPrim(typeId: any, id: any) { 443 | // console.log('Creating SPrim: ' + typeId + ' ' + id); 444 | 445 | if (typeId === 'material') { 446 | let material = new HydraMaterial(id, this); 447 | this.materials[id] = material; 448 | return material; 449 | } else { 450 | return undefined; 451 | } 452 | } 453 | 454 | CommitResources() { 455 | for (const id in this.meshes) { 456 | const hydraMesh = this.meshes[id] 457 | hydraMesh.commit(); 458 | } 459 | } 460 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | declare global { 3 | interface Window { getUsdModule: any; } 4 | } 5 | 6 | export async function loadWasmUSD(dir: string = '') { 7 | await includeScript(`${dir}/emHdBindings.js`); 8 | const module = await window.getUsdModule(null, dir); 9 | await module.ready; 10 | return module; 11 | } 12 | 13 | export async function fetchArrayBuffer(src: string) { 14 | const usdFile = await new Promise((resolve, reject) => { 15 | const req = new XMLHttpRequest(); 16 | req.open('GET', src, true); 17 | req.responseType = 'arraybuffer'; 18 | req.onload = () => req.response ? resolve(req.response) : reject(); 19 | req.send(null); 20 | }); 21 | 22 | return new Uint8Array(usdFile as any); 23 | } 24 | 25 | export function includeScript(url: string) { 26 | return new Promise(r => { 27 | const script = document.createElement('script'); 28 | script.src = url; 29 | script.onload = () => r(null); 30 | document.getElementsByTagName('head')[0].appendChild(script); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "skipLibCheck": true, 4 | "target": "es2022", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "lib": ["esnext", "dom"], 8 | "experimentalDecorators": true, 9 | "useDefineForClassFields": false, 10 | "allowSyntheticDefaultImports": false, 11 | "resolveJsonModule": true, 12 | "outDir": "./dist/lib", 13 | "declaration": true, 14 | "types": ["jasmine"], 15 | "alwaysStrict": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "strictFunctionTypes": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "strictNullChecks": false, 24 | "sourceMap": false, 25 | "inlineSourceMap": false, 26 | "importHelpers": true, 27 | "baseUrl": "./", 28 | "rootDir": "./src", 29 | "paths": { 30 | "usd-viewer/*": ["./src/*"] 31 | } 32 | }, 33 | "exclude": ["dist", "node_modules"] 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "types": [], 5 | "incremental": true, 6 | "isolatedModules": true, 7 | "useDefineForClassFields": true, 8 | "noEmit": true, 9 | "tsBuildInfoFile": "./dist/lib/.tsbuildinfo" 10 | }, 11 | "exclude": [ 12 | "dist", 13 | "node_modules" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /usd/README.md: -------------------------------------------------------------------------------- 1 | # Source 2 | 3 | These models are sourced from [NASA](https://nasa3d.arc.nasa.gov/) and the [Smithsonian 3d](https://www.si.edu/termsofuse) projects. 4 | 5 | - https://solarsystem.nasa.gov/resources/2401/cassini-3d-model/ 6 | - https://mars.nasa.gov/resources/25043/mars-ingenuity-helicopter-3d-model/ 7 | - https://mars.nasa.gov/resources/25042/mars-perseverance-rover-3d-model/ 8 | - https://3d.si.edu/object/3d/bell-x-1:6c69a6bb-55e6-4356-8725-120ff7f8d652 9 | -------------------------------------------------------------------------------- /usd/bell-x1.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coryrylan/usd-viewer/5b12bb2025e2a4a15226f6220f32341724163b07/usd/bell-x1.usdz -------------------------------------------------------------------------------- /usd/cassini.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coryrylan/usd-viewer/5b12bb2025e2a4a15226f6220f32341724163b07/usd/cassini.usdz -------------------------------------------------------------------------------- /usd/ingenuity.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coryrylan/usd-viewer/5b12bb2025e2a4a15226f6220f32341724163b07/usd/ingenuity.usdz -------------------------------------------------------------------------------- /usd/perseverance.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coryrylan/usd-viewer/5b12bb2025e2a4a15226f6220f32341724163b07/usd/perseverance.usdz -------------------------------------------------------------------------------- /wasm/README.md: -------------------------------------------------------------------------------- 1 | # Source 2 | 3 | The WASM content in this directory is sourced from the following Autodesk project 4 | https://github.com/autodesk-forks/USD/tree/gh-pages/usd_for_web_demos -------------------------------------------------------------------------------- /wasm/emHdBindings.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coryrylan/usd-viewer/5b12bb2025e2a4a15226f6220f32341724163b07/wasm/emHdBindings.wasm -------------------------------------------------------------------------------- /wasm/emHdBindings.worker.js: -------------------------------------------------------------------------------- 1 | var initializedJS=false;var Module={};function assert(condition,text){if(!condition)abort("Assertion failed: "+text)}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var out=function(){throw"out() is not defined in worker.js."};var err=threadPrintErr;this.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);Module["wasmModule"]=null;receiveInstance(instance);return instance.exports};this.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}getUsdModule(Module).then(function(instance){Module=instance;postMessage({"cmd":"loaded"})})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;var threadInfoStruct=e.data.threadInfoStruct;Module["__emscripten_thread_init"](threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;assert(threadInfoStruct);assert(top!=0);assert(max!=0);assert(top>max);Module["establishStackSpace"](top,max);Module["_emscripten_tls_init"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].setThreadStatus(Module["_pthread_self"](),1);if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);Module["checkStackCookie"]();if(!Module["getNoExitRuntime"]())Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(typeof Module["_emscripten_futex_wake"]!=="function"){err("Thread Initialisation failed.");throw ex}if(ex instanceof Module["ExitStatus"]){if(Module["getNoExitRuntime"]()){err("Pthread 0x"+_pthread_self().toString(16)+" called exit(), staying alive due to noExitRuntime.")}else{err("Pthread 0x"+_pthread_self().toString(16)+" called exit(), calling threadExit.");Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}else{err("Pthread 0x"+threadInfoStruct.toString(16)+" completed its pthread main entry point with an unwind, keeping the pthread worker alive for asynchronous operation.")}}}else if(e.data.cmd==="cancel"){if(threadInfoStruct){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(threadInfoStruct){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){self={location:{href:__filename}};var onmessage=this.onmessage;var nodeWorkerThreads=require("worker_threads");global.Worker=nodeWorkerThreads.Worker;var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");var nodeRead=function(filename){return nodeFS.readFileSync(filename,"utf8")};function globalEval(x){global.require=require;global.Module=Module;eval.call(null,x)}importScripts=function(f){globalEval(nodeRead(f))};postMessage=function(msg){parentPort.postMessage(msg)};if(typeof performance==="undefined"){performance={now:function(){return Date.now()}}}} --------------------------------------------------------------------------------