@needle-tools/usd 1.0.0-next.d536d99 → 1.0.0-next.d840b4c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +207 -33
- package/package.json +3 -2
- package/src/bindings/emHdBindings.wasm +0 -0
- package/src/create.three.js +4 -1
- package/src/hydra/ThreeJsRenderDelegate.js +173 -6
- package/src/plugins/needle.js +1 -0
- package/src/types/create.three.d.ts +9 -0
- package/src/types/plugins.d.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ All notable changes to this package will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.0.0-next.1] - 2026-06-29
|
|
8
|
+
### Added
|
|
9
|
+
- Added optional material readiness policy support for loaders that need to wait for asynchronous material generation and texture loading before reporting completion.
|
|
10
|
+
- Added matrix coverage for USD Working Group assets, Needle Engine runtime shapes, public viewer lifecycle cleanup, authored USD concepts, and culling/material edge cases.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Improved the existing Needle Engine integration so the Engine loader/component path uses the updated USD runtime, readiness, and cleanup behavior alongside the direct three.js Hydra path.
|
|
14
|
+
- Updated the public viewer examples so original glTF samples use the regular glTF loader path instead of wrapping glTF assets in USD.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Fixed Hydra lifecycle cleanup when switching assets so stale RPrim/SPrim state and three.js resources are destroyed instead of accumulating across loads.
|
|
18
|
+
- Addressed repeated-load resource leaks that could cause out-of-memory failures after cycling between large USDZ assets.
|
|
19
|
+
- Improved browser asset resolution for nested package paths, authored relative paths, parent-directory references, USDZ-embedded textures, GLB-embedded texture assets, and MaterialX texture assets.
|
|
20
|
+
- Fixed known MaterialX and UsdPreviewSurface texture regressions, including separate metallic/roughness/occlusion maps, packed texture updates after async loads, texture transforms, procedural texture fixtures, and noise/bricks sample coverage.
|
|
21
|
+
- Fixed material culling so materials are only cloned when the same authored material is used with conflicting sidedness, preserving animation bindings for the common shared-material path.
|
|
22
|
+
- Fixed USD-authored normals/interpolation handling to better match OpenUSD/usdview flat-vs-smooth behavior.
|
|
23
|
+
- Fixed single-sided/double-sided rendering for USD meshes and added shared-material culling tests.
|
|
24
|
+
- Fixed variant and payload switching so old selections are removed instead of double-rendering alongside the new composed result.
|
|
25
|
+
- Fixed up-axis setup before the first Hydra draw and across repeated stage loads so one asset cannot leak orientation state into the next.
|
|
26
|
+
- Fixed camera/light SPrim routing and expanded coverage for visibility, purpose switching, native instances, point instancers, and nested package references.
|
|
27
|
+
- Fixed Needle Engine loader timing and readiness so USD stages, materials, and animations initialize consistently through the Engine loader path.
|
|
28
|
+
- Fixed three.js/Needle Engine import compatibility across vanilla three.js and Engine-provided three.js runtime mappings.
|
|
29
|
+
|
|
7
30
|
## [1.0.0-next.0] - 2026-06-25
|
|
8
31
|
- Modernized the wasm runtime to upstream OpenUSD 26.05 with Hydra imaging enabled for the three.js render delegate.
|
|
9
32
|
- Added the Adobe glTF file-format plugin, MaterialX support through Hydra material documents, and OpenSubdiv-enabled builds.
|
package/README.md
CHANGED
|
@@ -8,12 +8,12 @@ For commercial use, please contact [hi@needle.tools](mailto:hi@needle.tools).
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
npm install @needle-tools/usd@
|
|
11
|
+
npm install @needle-tools/usd@1.0.0 three
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Version 1.0 uses upstream OpenUSD 26.05 and ships a Hydra imaging bridge for
|
|
15
|
+
three.js. The wasm bundle includes Adobe `usdGltf`, MaterialX, and OpenSubdiv
|
|
16
|
+
support.
|
|
17
17
|
|
|
18
18
|
## Runtime Requirements
|
|
19
19
|
|
|
@@ -41,44 +41,218 @@ The modern Emscripten output contains `emHdBindings.js` and
|
|
|
41
41
|
`emHdBindings.wasm`. It does not ship a separate `emHdBindings.worker.js`; the
|
|
42
42
|
pthread workers load the main generated JavaScript entrypoint directly.
|
|
43
43
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
## Minimal Examples
|
|
45
|
+
|
|
46
|
+
All browser examples must be served with the COOP/COEP headers from
|
|
47
|
+
["Runtime Requirements"](#runtime-requirements). The import-map examples use
|
|
48
|
+
same-origin `/vendor/...` URLs so the threaded wasm worker can load the
|
|
49
|
+
Emscripten JavaScript from the page origin; replace those URLs with your own
|
|
50
|
+
served package paths.
|
|
51
|
+
|
|
52
|
+
### three.js With Import Map
|
|
53
|
+
|
|
54
|
+
`index.html`
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<!doctype html>
|
|
58
|
+
<html>
|
|
59
|
+
<body style="margin:0">
|
|
60
|
+
<script type="importmap">
|
|
61
|
+
{
|
|
62
|
+
"imports": {
|
|
63
|
+
"three": "/vendor/three/build/three.module.js",
|
|
64
|
+
"three/addons/": "/vendor/three/examples/jsm/",
|
|
65
|
+
"@needle-tools/materialx": "/vendor/@needle-tools/materialx/index.js",
|
|
66
|
+
"@needle-tools/usd": "/vendor/@needle-tools/usd/src/index.js",
|
|
67
|
+
"@needle-tools/usd/three": "/vendor/@needle-tools/usd/src/create.three.js"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
<script type="module">
|
|
72
|
+
import * as THREE from "three";
|
|
73
|
+
import { getUsdModule } from "@needle-tools/usd";
|
|
74
|
+
import { createThreeHydra } from "@needle-tools/usd/three";
|
|
75
|
+
|
|
76
|
+
const scene = new THREE.Scene();
|
|
77
|
+
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.01, 1000);
|
|
78
|
+
camera.position.set(0, 1.5, 4);
|
|
79
|
+
|
|
80
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
81
|
+
renderer.setSize(innerWidth, innerHeight);
|
|
82
|
+
document.body.append(renderer.domElement);
|
|
83
|
+
|
|
84
|
+
const usd = await getUsdModule();
|
|
85
|
+
const handle = await createThreeHydra({
|
|
86
|
+
USD: usd,
|
|
87
|
+
scene,
|
|
88
|
+
url: "./model.usdz"
|
|
89
|
+
});
|
|
90
|
+
await handle.ready();
|
|
91
|
+
// By default ready() waits for the stage and first Hydra draw, but not for
|
|
92
|
+
// async material generation. Pass waitForMaterials: true when correctness
|
|
93
|
+
// requires a material/texture barrier, or await handle.materialsReady().
|
|
94
|
+
|
|
95
|
+
let last = performance.now();
|
|
96
|
+
renderer.setAnimationLoop((time) => {
|
|
97
|
+
const dt = (time - last) / 1000;
|
|
98
|
+
last = time;
|
|
99
|
+
handle.update(dt);
|
|
100
|
+
renderer.render(scene, camera);
|
|
101
|
+
});
|
|
102
|
+
</script>
|
|
103
|
+
</body>
|
|
104
|
+
</html>
|
|
105
|
+
```
|
|
51
106
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
107
|
+
### three.js With Package Install
|
|
108
|
+
|
|
109
|
+
`package.json`
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"type": "module",
|
|
114
|
+
"scripts": {
|
|
115
|
+
"dev": "vite --host 127.0.0.1"
|
|
116
|
+
},
|
|
117
|
+
"dependencies": {
|
|
118
|
+
"@needle-tools/usd": "1.0.0",
|
|
119
|
+
"three": "^0.185.0",
|
|
120
|
+
"vite": "^8.1.0"
|
|
121
|
+
}
|
|
57
122
|
}
|
|
58
123
|
```
|
|
59
124
|
|
|
125
|
+
`index.html`
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<!doctype html>
|
|
129
|
+
<html>
|
|
130
|
+
<body style="margin:0">
|
|
131
|
+
<script type="module">
|
|
132
|
+
import * as THREE from "three";
|
|
133
|
+
import { getUsdModule } from "@needle-tools/usd";
|
|
134
|
+
import { createThreeHydra } from "@needle-tools/usd/three";
|
|
135
|
+
|
|
136
|
+
const scene = new THREE.Scene();
|
|
137
|
+
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.01, 1000);
|
|
138
|
+
camera.position.set(0, 1.5, 4);
|
|
139
|
+
|
|
140
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
141
|
+
renderer.setSize(innerWidth, innerHeight);
|
|
142
|
+
document.body.append(renderer.domElement);
|
|
143
|
+
|
|
144
|
+
const usd = await getUsdModule();
|
|
145
|
+
const handle = await createThreeHydra({
|
|
146
|
+
USD: usd,
|
|
147
|
+
scene,
|
|
148
|
+
url: "./model.usdz"
|
|
149
|
+
});
|
|
150
|
+
await handle.ready();
|
|
151
|
+
|
|
152
|
+
let last = performance.now();
|
|
153
|
+
renderer.setAnimationLoop((time) => {
|
|
154
|
+
const dt = (time - last) / 1000;
|
|
155
|
+
last = time;
|
|
156
|
+
handle.update(dt);
|
|
157
|
+
renderer.render(scene, camera);
|
|
158
|
+
});
|
|
159
|
+
</script>
|
|
160
|
+
</body>
|
|
161
|
+
</html>
|
|
162
|
+
```
|
|
60
163
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
164
|
+
For Vite projects, add the `needleUSD()` plugin shown above so dev serving uses
|
|
165
|
+
the required COOP/COEP headers.
|
|
166
|
+
|
|
167
|
+
### Needle Engine With Import Map
|
|
168
|
+
|
|
169
|
+
`index.html`
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
<!doctype html>
|
|
173
|
+
<html>
|
|
174
|
+
<body style="margin:0">
|
|
175
|
+
<script type="importmap">
|
|
176
|
+
{
|
|
177
|
+
"imports": {
|
|
178
|
+
"three": "/vendor/@needle-tools/engine/dist/three.min.js",
|
|
179
|
+
"three/addons/": "/vendor/@needle-tools/three/examples/jsm/",
|
|
180
|
+
"@needle-tools/engine": "/vendor/@needle-tools/engine/dist/needle-engine.min.js",
|
|
181
|
+
"@needle-tools/materialx": "/vendor/@needle-tools/materialx/index.js",
|
|
182
|
+
"@needle-tools/usd": "/vendor/@needle-tools/usd/src/index.js",
|
|
183
|
+
"@needle-tools/usd/three": "/vendor/@needle-tools/usd/src/create.three.js",
|
|
184
|
+
"@needle-tools/usd/plugins": "/vendor/@needle-tools/usd/src/plugins/index.js"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
</script>
|
|
188
|
+
<script type="module">
|
|
189
|
+
import "@needle-tools/engine";
|
|
190
|
+
import { addPluginForNeedleEngine } from "@needle-tools/usd/plugins";
|
|
191
|
+
|
|
192
|
+
await addPluginForNeedleEngine({
|
|
193
|
+
// Defaults to non-blocking materials. Set waitForMaterials: true if the
|
|
194
|
+
// Needle loader should not report ready until materials/textures settle.
|
|
195
|
+
getFiles: () => []
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
document.body.insertAdjacentHTML(
|
|
199
|
+
"beforeend",
|
|
200
|
+
'<needle-engine src="./model.usdz" camera-controls contactshadows="0.7"></needle-engine>'
|
|
201
|
+
);
|
|
202
|
+
</script>
|
|
203
|
+
</body>
|
|
204
|
+
</html>
|
|
205
|
+
```
|
|
65
206
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
207
|
+
### Needle Engine With Package Install
|
|
208
|
+
|
|
209
|
+
`package.json`
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"type": "module",
|
|
214
|
+
"scripts": {
|
|
215
|
+
"dev": "vite --host 127.0.0.1"
|
|
216
|
+
},
|
|
217
|
+
"dependencies": {
|
|
218
|
+
"@needle-tools/engine": "^5.1.2",
|
|
219
|
+
"@needle-tools/usd": "1.0.0",
|
|
220
|
+
"three": "npm:@needle-tools/three@^0.169.19",
|
|
221
|
+
"vite": "^8.1.0"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
69
225
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
226
|
+
`index.html`
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<!doctype html>
|
|
230
|
+
<html>
|
|
231
|
+
<body style="margin:0">
|
|
232
|
+
<script type="module">
|
|
233
|
+
import "@needle-tools/engine";
|
|
234
|
+
import { addPluginForNeedleEngine } from "@needle-tools/usd/plugins";
|
|
235
|
+
|
|
236
|
+
await addPluginForNeedleEngine({
|
|
237
|
+
// Defaults to non-blocking materials. Set waitForMaterials: true if the
|
|
238
|
+
// Needle loader should not report ready until materials/textures settle.
|
|
239
|
+
getFiles: () => []
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
document.body.insertAdjacentHTML(
|
|
243
|
+
"beforeend",
|
|
244
|
+
'<needle-engine src="./model.usdz" camera-controls contactshadows="0.7"></needle-engine>'
|
|
245
|
+
);
|
|
246
|
+
</script>
|
|
247
|
+
</body>
|
|
248
|
+
</html>
|
|
79
249
|
```
|
|
80
250
|
|
|
81
|
-
|
|
251
|
+
For folder/drop workflows, return the active file set from `getFiles()`. The
|
|
252
|
+
first file must be the root USD file, and each file should have a stable `path`
|
|
253
|
+
property so USD references can resolve.
|
|
254
|
+
|
|
255
|
+
## Public Entrypoints
|
|
82
256
|
|
|
83
257
|
```js
|
|
84
258
|
import { getUsdModule, loadOpenUsdBuildInfo } from "@needle-tools/usd";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/usd",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.d840b4c",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"types": "src/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -62,11 +62,12 @@
|
|
|
62
62
|
"three-matrix:cache": "node scripts/cache-three-matrix.mjs",
|
|
63
63
|
"test:bindings": "node --test tests/bindings/*.test.mjs",
|
|
64
64
|
"test:needle-engine-matrix": "npm run needle-engine-matrix:cache && playwright test -c tests/needle-engine-matrix/playwright.config.ts",
|
|
65
|
+
"test:public-viewer-lifecycle": "playwright test -c tests/public-viewer/playwright.config.ts",
|
|
65
66
|
"test:three-matrix": "node scripts/run-three-matrix.mjs",
|
|
66
67
|
"test:viewer-visual": "playwright test -c tests/viewer-visual/playwright.config.ts"
|
|
67
68
|
},
|
|
68
69
|
"devDependencies": {
|
|
69
|
-
"@needle-tools/engine": "^5.1.2",
|
|
70
|
+
"@needle-tools/engine": "^5.1.2-canary.1782551556.isaac-newton.7c76e45",
|
|
70
71
|
"@needle-tools/three-test-matrix": "^0.1.0",
|
|
71
72
|
"@playwright/test": "^1.60.0",
|
|
72
73
|
"@types/three": "^0.164.1",
|
|
Binary file
|
package/src/create.three.js
CHANGED
|
@@ -349,6 +349,9 @@ export async function createThreeHydra(config) {
|
|
|
349
349
|
|
|
350
350
|
/** Draw once, after stage metadata has been applied to the root scene. */
|
|
351
351
|
const initialDrawPromise = draw();
|
|
352
|
+
const readyPromise = config.waitForMaterials
|
|
353
|
+
? initialDrawPromise.then(() => renderInterface.waitForMaterialsReady())
|
|
354
|
+
: initialDrawPromise;
|
|
352
355
|
|
|
353
356
|
let time = 0;
|
|
354
357
|
let currentTimeCode = stageStartTimeCode;
|
|
@@ -383,7 +386,7 @@ export async function createThreeHydra(config) {
|
|
|
383
386
|
|
|
384
387
|
return {
|
|
385
388
|
driver: /** @type {import(".").HdWebSyncDriver} */ (driverOrPromise),
|
|
386
|
-
ready: () =>
|
|
389
|
+
ready: () => readyPromise,
|
|
387
390
|
update: (dt) => {
|
|
388
391
|
// ensure we're not dead
|
|
389
392
|
if (driver.isDeleted()) {
|
|
@@ -6,6 +6,8 @@ const {
|
|
|
6
6
|
TextureLoader,
|
|
7
7
|
BufferGeometry,
|
|
8
8
|
MeshPhysicalMaterial,
|
|
9
|
+
FrontSide,
|
|
10
|
+
BackSide,
|
|
9
11
|
DoubleSide,
|
|
10
12
|
Color,
|
|
11
13
|
Mesh,
|
|
@@ -80,6 +82,31 @@ function disposeObjectResources(object) {
|
|
|
80
82
|
object.parent?.remove(object);
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
function isFiniteArray(values, dimension = 3) {
|
|
86
|
+
if (!values || values.length === 0 || values.length % dimension !== 0) return false;
|
|
87
|
+
for (let i = 0; i < values.length; i++) {
|
|
88
|
+
if (!Number.isFinite(values[i])) return false;
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function cullStyleToThreeSide(doubleSided, cullStyle) {
|
|
94
|
+
switch (cullStyle) {
|
|
95
|
+
case "nothing":
|
|
96
|
+
return DoubleSide;
|
|
97
|
+
case "back":
|
|
98
|
+
return FrontSide;
|
|
99
|
+
case "front":
|
|
100
|
+
return BackSide;
|
|
101
|
+
case "frontUnlessDoubleSided":
|
|
102
|
+
return doubleSided ? DoubleSide : BackSide;
|
|
103
|
+
case "backUnlessDoubleSided":
|
|
104
|
+
case "dontCare":
|
|
105
|
+
default:
|
|
106
|
+
return doubleSided ? DoubleSide : FrontSide;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
83
110
|
function primNameFromPath(id, fallback) {
|
|
84
111
|
const path = String(id || "");
|
|
85
112
|
const slash = path.lastIndexOf("/");
|
|
@@ -258,8 +285,6 @@ class TextureRegistry {
|
|
|
258
285
|
} else if (extension === 'jpeg') {
|
|
259
286
|
filetype = 'image/jpeg';
|
|
260
287
|
} else if (extension === 'exr') {
|
|
261
|
-
console.warn("EXR textures are not fully supported yet", resourcePath);
|
|
262
|
-
// using EXRLoader explicitly
|
|
263
288
|
filetype = 'image/x-exr';
|
|
264
289
|
} else if (extension === 'tga') {
|
|
265
290
|
console.warn("TGA textures are not fully supported yet", resourcePath);
|
|
@@ -527,6 +552,8 @@ class HydraMesh {
|
|
|
527
552
|
this._uvs = undefined;
|
|
528
553
|
this._indices = undefined;
|
|
529
554
|
this._materials = [];
|
|
555
|
+
this._materialSideClones = new Map();
|
|
556
|
+
this._side = DoubleSide;
|
|
530
557
|
this._visible = false;
|
|
531
558
|
this._renderTag = 'geometry';
|
|
532
559
|
this._instancedMesh = null;
|
|
@@ -540,6 +567,7 @@ class HydraMesh {
|
|
|
540
567
|
this._ownedMaterial = material;
|
|
541
568
|
this._materials.push(material);
|
|
542
569
|
this._mesh = new Mesh(this._geometry, material);
|
|
570
|
+
this._installMeshHooks(this._mesh);
|
|
543
571
|
this._mesh.visible = false;
|
|
544
572
|
this._mesh.castShadow = true;
|
|
545
573
|
this._mesh.receiveShadow = true;
|
|
@@ -561,6 +589,7 @@ class HydraMesh {
|
|
|
561
589
|
if (!this._mesh) return;
|
|
562
590
|
this._interface.unassignMeshFromMaterials(this._mesh);
|
|
563
591
|
this._disposeInstancedMesh();
|
|
592
|
+
this._disposeMaterialSideClones();
|
|
564
593
|
if (this._mesh.parent) {
|
|
565
594
|
this._mesh.parent.remove(this._mesh);
|
|
566
595
|
}
|
|
@@ -573,13 +602,25 @@ class HydraMesh {
|
|
|
573
602
|
updateOrder(attribute, attributeName, dimension = 3) {
|
|
574
603
|
if (debugMeshes) console.log("updateOrder", attribute, attributeName, dimension);
|
|
575
604
|
if (attribute && this._indices) {
|
|
605
|
+
if (!isFiniteArray(attribute, dimension)) {
|
|
606
|
+
this._geometry.deleteAttribute(attributeName);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
576
609
|
let values = [];
|
|
577
610
|
for (let i = 0; i < this._indices.length; i++) {
|
|
578
611
|
let index = this._indices[i]
|
|
612
|
+
if (!Number.isInteger(index) || index < 0 || (dimension * index + dimension) > attribute.length) {
|
|
613
|
+
this._geometry.deleteAttribute(attributeName);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
579
616
|
for (let j = 0; j < dimension; ++j) {
|
|
580
617
|
values.push(attribute[dimension * index + j]);
|
|
581
618
|
}
|
|
582
619
|
}
|
|
620
|
+
if (!isFiniteArray(values, dimension)) {
|
|
621
|
+
this._geometry.deleteAttribute(attributeName);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
583
624
|
this._geometry.setAttribute(attributeName, new Float32BufferAttribute(values, dimension));
|
|
584
625
|
if (attributeName === 'position') {
|
|
585
626
|
this._geometry.computeBoundingBox();
|
|
@@ -628,6 +669,7 @@ class HydraMesh {
|
|
|
628
669
|
if (!this._instancedMesh || this._instancedMesh.count !== instanceCount) {
|
|
629
670
|
this._disposeInstancedMesh();
|
|
630
671
|
this._instancedMesh = new InstancedMesh(this._geometry, this._mesh.material, instanceCount);
|
|
672
|
+
this._installMeshHooks(this._instancedMesh);
|
|
631
673
|
this._instancedMesh.name = `${this._mesh.name}_instances`;
|
|
632
674
|
this._instancedMesh.castShadow = this._mesh.castShadow;
|
|
633
675
|
this._instancedMesh.receiveShadow = this._mesh.receiveShadow;
|
|
@@ -662,6 +704,78 @@ class HydraMesh {
|
|
|
662
704
|
this._instancedMesh = null;
|
|
663
705
|
}
|
|
664
706
|
|
|
707
|
+
_installMeshHooks(mesh) {
|
|
708
|
+
mesh.userData.usdPath = this._id;
|
|
709
|
+
mesh.userData.usdHydraMaterialSide = this._side;
|
|
710
|
+
mesh.userData.usdHydraApplyMaterialSide = (material, hydraMaterial) => this._applyMaterialSide(material, hydraMaterial);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
_disposeMaterialSideClones() {
|
|
714
|
+
for (const material of this._materialSideClones.values()) {
|
|
715
|
+
material.dispose?.();
|
|
716
|
+
}
|
|
717
|
+
this._materialSideClones.clear();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
_updateMeshSideState() {
|
|
721
|
+
if (this._mesh) {
|
|
722
|
+
this._mesh.userData.usdHydraMaterialSide = this._side;
|
|
723
|
+
}
|
|
724
|
+
if (this._instancedMesh) {
|
|
725
|
+
this._instancedMesh.userData.usdHydraMaterialSide = this._side;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
_applyMaterialSide(material, hydraMaterial = null) {
|
|
730
|
+
if (!material) return material;
|
|
731
|
+
if (Array.isArray(material)) {
|
|
732
|
+
return material.map(entry => this._applyMaterialSide(entry, hydraMaterial));
|
|
733
|
+
}
|
|
734
|
+
if (material === this._ownedMaterial || material.userData?.usdHydraMeshOwner === this._id) {
|
|
735
|
+
material.side = this._side;
|
|
736
|
+
material.needsUpdate = true;
|
|
737
|
+
return material;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (!hydraMaterial?.requiresMaterialSideVariants?.()) {
|
|
741
|
+
material.side = this._side;
|
|
742
|
+
material.needsUpdate = true;
|
|
743
|
+
return material;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
let clone = this._materialSideClones.get(material);
|
|
747
|
+
if (!clone) {
|
|
748
|
+
clone = material.clone();
|
|
749
|
+
clone.userData.usdHydraSideCloneOf = material.uuid;
|
|
750
|
+
this._materialSideClones.set(material, clone);
|
|
751
|
+
} else {
|
|
752
|
+
clone.copy(material);
|
|
753
|
+
clone.userData.usdHydraSideCloneOf = material.uuid;
|
|
754
|
+
}
|
|
755
|
+
clone.side = this._side;
|
|
756
|
+
clone.needsUpdate = true;
|
|
757
|
+
return clone;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
_applyCullSideToMeshes() {
|
|
761
|
+
this._updateMeshSideState();
|
|
762
|
+
let refreshedMaterialAssignments = false;
|
|
763
|
+
if (this._mesh) {
|
|
764
|
+
refreshedMaterialAssignments = this._interface.refreshMeshMaterialAssignments(this._mesh);
|
|
765
|
+
if (!refreshedMaterialAssignments) {
|
|
766
|
+
this._mesh.material = this._applyMaterialSide(this._mesh.material);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (this._instancedMesh) {
|
|
770
|
+
this._instancedMesh.material = this._mesh?.material;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
setCullStyle(doubleSided, cullStyle) {
|
|
775
|
+
this._side = cullStyleToThreeSide(Boolean(doubleSided), String(cullStyle || "dontCare"));
|
|
776
|
+
this._applyCullSideToMeshes();
|
|
777
|
+
}
|
|
778
|
+
|
|
665
779
|
setVisibilityState(visible, renderTag = 'geometry') {
|
|
666
780
|
this._visible = Boolean(visible);
|
|
667
781
|
this._renderTag = String(renderTag || 'geometry');
|
|
@@ -687,6 +801,14 @@ class HydraMesh {
|
|
|
687
801
|
this.updateOrder(this._normals, 'normal');
|
|
688
802
|
}
|
|
689
803
|
|
|
804
|
+
updateOrderedNormals(normals) {
|
|
805
|
+
// don't apply automatically generated normals if there are already authored normals.
|
|
806
|
+
if (this._geometry.hasAttribute('normal')) return;
|
|
807
|
+
|
|
808
|
+
this._normals = normals.slice(0);
|
|
809
|
+
this._geometry.setAttribute('normal', new Float32BufferAttribute(this._normals, 3));
|
|
810
|
+
}
|
|
811
|
+
|
|
690
812
|
setNormals(data, interpolation) {
|
|
691
813
|
if (interpolation === 'facevarying') {
|
|
692
814
|
// The UV buffer has already been prepared on the C++ side, so we just set it
|
|
@@ -735,7 +857,9 @@ class HydraMesh {
|
|
|
735
857
|
this._mesh.parent.remove(this._mesh);
|
|
736
858
|
}
|
|
737
859
|
this._mesh = new Mesh(this._geometry, this._materials);
|
|
860
|
+
this._installMeshHooks(this._mesh);
|
|
738
861
|
this.setVisibilityState(this._visible, this._renderTag);
|
|
862
|
+
this._applyCullSideToMeshes();
|
|
739
863
|
this._interface.config.usdRoot.add(this._mesh);
|
|
740
864
|
|
|
741
865
|
for (let i = 0; i < sections.length; i++) {
|
|
@@ -750,8 +874,10 @@ class HydraMesh {
|
|
|
750
874
|
let wasDefaultMaterial = false;
|
|
751
875
|
if (this._mesh.material === defaultMaterial) {
|
|
752
876
|
this._mesh.material = this._mesh.material.clone();
|
|
877
|
+
this._mesh.material.userData.usdHydraMeshOwner = this._id;
|
|
753
878
|
wasDefaultMaterial = true;
|
|
754
879
|
}
|
|
880
|
+
this._mesh.material = this._applyMaterialSide(this._mesh.material);
|
|
755
881
|
|
|
756
882
|
this._colors = null;
|
|
757
883
|
|
|
@@ -937,11 +1063,15 @@ class HydraMaterial {
|
|
|
937
1063
|
if (!existing) {
|
|
938
1064
|
this._assignments.push({ mesh, materialIndex });
|
|
939
1065
|
}
|
|
940
|
-
this.
|
|
1066
|
+
this._applyMaterialToAssignedMeshes();
|
|
941
1067
|
}
|
|
942
1068
|
|
|
943
1069
|
unassignMesh(mesh) {
|
|
1070
|
+
const previousLength = this._assignments.length;
|
|
944
1071
|
this._assignments = this._assignments.filter(assignment => assignment.mesh !== mesh);
|
|
1072
|
+
if (this._assignments.length !== previousLength) {
|
|
1073
|
+
this._applyMaterialToAssignedMeshes();
|
|
1074
|
+
}
|
|
945
1075
|
}
|
|
946
1076
|
|
|
947
1077
|
dispose() {
|
|
@@ -951,17 +1081,22 @@ class HydraMaterial {
|
|
|
951
1081
|
}
|
|
952
1082
|
|
|
953
1083
|
_applyMaterialToMesh(mesh, materialIndex) {
|
|
1084
|
+
const applyMaterialSide = mesh.userData?.usdHydraApplyMaterialSide;
|
|
1085
|
+
const material = typeof applyMaterialSide === 'function'
|
|
1086
|
+
? applyMaterialSide(this._material, this)
|
|
1087
|
+
: this._material;
|
|
1088
|
+
|
|
954
1089
|
if (materialIndex === null || materialIndex === undefined) {
|
|
955
|
-
mesh.material =
|
|
1090
|
+
mesh.material = material;
|
|
956
1091
|
return;
|
|
957
1092
|
}
|
|
958
1093
|
|
|
959
1094
|
if (Array.isArray(mesh.material)) {
|
|
960
|
-
mesh.material[materialIndex] =
|
|
1095
|
+
mesh.material[materialIndex] = material;
|
|
961
1096
|
return;
|
|
962
1097
|
}
|
|
963
1098
|
|
|
964
|
-
mesh.material =
|
|
1099
|
+
mesh.material = material;
|
|
965
1100
|
}
|
|
966
1101
|
|
|
967
1102
|
_applyMaterialToAssignedMeshes() {
|
|
@@ -970,6 +1105,28 @@ class HydraMaterial {
|
|
|
970
1105
|
}
|
|
971
1106
|
}
|
|
972
1107
|
|
|
1108
|
+
hasMeshAssignment(mesh) {
|
|
1109
|
+
return this._assignments.some(assignment => assignment.mesh === mesh);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
requiresMaterialSideVariants() {
|
|
1113
|
+
const sides = new Set();
|
|
1114
|
+
for (const assignment of this._assignments) {
|
|
1115
|
+
const side = assignment.mesh?.userData?.usdHydraMaterialSide;
|
|
1116
|
+
if (typeof side === 'number') {
|
|
1117
|
+
sides.add(side);
|
|
1118
|
+
}
|
|
1119
|
+
if (sides.size > 1) {
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
refreshAssignments() {
|
|
1127
|
+
this._applyMaterialToAssignedMeshes();
|
|
1128
|
+
}
|
|
1129
|
+
|
|
973
1130
|
beginMaterialSync() {
|
|
974
1131
|
this._nodes = {};
|
|
975
1132
|
this._resolvedAssetPaths.clear();
|
|
@@ -1749,6 +1906,16 @@ export class ThreeRenderDelegateInterface {
|
|
|
1749
1906
|
}
|
|
1750
1907
|
}
|
|
1751
1908
|
|
|
1909
|
+
refreshMeshMaterialAssignments(mesh) {
|
|
1910
|
+
let refreshed = false;
|
|
1911
|
+
for (const material of Object.values(this.materials)) {
|
|
1912
|
+
if (!material.hasMeshAssignment(mesh)) continue;
|
|
1913
|
+
material.refreshAssignments();
|
|
1914
|
+
refreshed = true;
|
|
1915
|
+
}
|
|
1916
|
+
return refreshed;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1752
1919
|
CommitResources() {
|
|
1753
1920
|
for (const id in this.meshes) {
|
|
1754
1921
|
const hydraMesh = this.meshes[id]
|
package/src/plugins/needle.js
CHANGED
|
@@ -71,6 +71,13 @@ export declare type createThreeHydraConfig = {
|
|
|
71
71
|
* only controls the helper/demo Three light brightness.
|
|
72
72
|
*/
|
|
73
73
|
scenePrimitiveLightIntensityScale?: number,
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Include asynchronous material generation and texture assignment in ready().
|
|
77
|
+
* Defaults to false so stage loading and first draw are not blocked by materials.
|
|
78
|
+
* Call handle.materialsReady() when you need an explicit material barrier.
|
|
79
|
+
*/
|
|
80
|
+
waitForMaterials?: boolean,
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
/**
|
|
@@ -109,6 +116,8 @@ export declare type NeedleThreeHydraHandle = {
|
|
|
109
116
|
*/
|
|
110
117
|
editStage: <T>(callback: (stage: USDStage, driver: HdWebSyncDriver) => T | Promise<T>) => Promise<T | undefined>,
|
|
111
118
|
/** Resolves after the initial Hydra draw has settled.
|
|
119
|
+
* If createThreeHydra was called with waitForMaterials, also waits for
|
|
120
|
+
* asynchronous material generation and texture assignment.
|
|
112
121
|
*/
|
|
113
122
|
ready: () => Promise<void>,
|
|
114
123
|
/** Resolves when asynchronous material generation and texture assignment have settled.
|
package/src/types/plugins.d.ts
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
export type PluginContext = {
|
|
4
4
|
debug?: boolean,
|
|
5
|
+
/**
|
|
6
|
+
* Include asynchronous material generation and texture assignment in the
|
|
7
|
+
* Hydra handle's ready() promise. Defaults to false.
|
|
8
|
+
*/
|
|
9
|
+
waitForMaterials?: boolean,
|
|
5
10
|
getFiles: () => Array<import("../types").HydraFile>
|
|
6
11
|
}
|
|
7
12
|
|