('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()}}}}
--------------------------------------------------------------------------------