@needle-tools/engine 3.3.0-alpha → 3.4.0-alpha

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/needle-engine.js +26116 -24881
  3. package/dist/needle-engine.min.js +384 -383
  4. package/dist/needle-engine.umd.cjs +372 -371
  5. package/lib/engine/codegen/register_types.js +50 -2
  6. package/lib/engine/codegen/register_types.js.map +1 -1
  7. package/lib/engine/engine_gameobject.d.ts +1 -1
  8. package/lib/engine/engine_gameobject.js +4 -2
  9. package/lib/engine/engine_gameobject.js.map +1 -1
  10. package/lib/engine/engine_three_utils.js +2 -2
  11. package/lib/engine/engine_three_utils.js.map +1 -1
  12. package/lib/engine-components/Animation.js +4 -0
  13. package/lib/engine-components/Animation.js.map +1 -1
  14. package/lib/engine-components/codegen/components.d.ts +25 -1
  15. package/lib/engine-components/codegen/components.js +25 -1
  16. package/lib/engine-components/codegen/components.js.map +1 -1
  17. package/lib/engine-components/export/usdz/Extension.d.ts +4 -4
  18. package/lib/engine-components/export/usdz/ThreeUSDZExporter.d.ts +86 -0
  19. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +830 -0
  20. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -0
  21. package/lib/engine-components/export/usdz/USDZExporter.d.ts +6 -3
  22. package/lib/engine-components/export/usdz/USDZExporter.js +34 -11
  23. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  24. package/lib/engine-components/export/usdz/extensions/Animation.d.ts +15 -15
  25. package/lib/engine-components/export/usdz/extensions/Animation.js +24 -29
  26. package/lib/engine-components/export/usdz/extensions/Animation.js.map +1 -1
  27. package/lib/engine-components/export/usdz/extensions/DocumentExtension.d.ts +5 -0
  28. package/lib/engine-components/export/usdz/extensions/DocumentExtension.js +7 -0
  29. package/lib/engine-components/export/usdz/extensions/DocumentExtension.js.map +1 -0
  30. package/lib/engine-components/export/usdz/extensions/USDZText.d.ts +47 -0
  31. package/lib/engine-components/export/usdz/extensions/USDZText.js +114 -0
  32. package/lib/engine-components/export/usdz/extensions/USDZText.js.map +1 -0
  33. package/lib/engine-components/export/usdz/extensions/behavior/Actions.d.ts +30 -0
  34. package/lib/engine-components/export/usdz/extensions/behavior/Actions.js +89 -0
  35. package/lib/engine-components/export/usdz/extensions/behavior/Actions.js.map +1 -0
  36. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.d.ts +23 -0
  37. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js +114 -0
  38. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js.map +1 -0
  39. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +96 -0
  40. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +421 -0
  41. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -0
  42. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.d.ts +111 -0
  43. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js +409 -0
  44. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js.map +1 -0
  45. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  46. package/lib/engine-components/ui/BaseUIComponent.d.ts +2 -0
  47. package/lib/engine-components/ui/BaseUIComponent.js +6 -0
  48. package/lib/engine-components/ui/BaseUIComponent.js.map +1 -1
  49. package/lib/engine-components/ui/Canvas.d.ts +11 -1
  50. package/lib/engine-components/ui/Canvas.js +72 -3
  51. package/lib/engine-components/ui/Canvas.js.map +1 -1
  52. package/lib/engine-components/ui/Graphic.js.map +1 -1
  53. package/lib/engine-components/ui/Image.js +4 -4
  54. package/lib/engine-components/ui/Image.js.map +1 -1
  55. package/lib/engine-components/ui/Interfaces.d.ts +11 -0
  56. package/lib/engine-components/ui/Interfaces.js +11 -0
  57. package/lib/engine-components/ui/Interfaces.js.map +1 -1
  58. package/lib/engine-components/ui/Layout.d.ts +65 -3
  59. package/lib/engine-components/ui/Layout.js +304 -3
  60. package/lib/engine-components/ui/Layout.js.map +1 -1
  61. package/lib/engine-components/ui/RectTransform.d.ts +8 -7
  62. package/lib/engine-components/ui/RectTransform.js +63 -35
  63. package/lib/engine-components/ui/RectTransform.js.map +1 -1
  64. package/lib/engine-components/utils/LookAt.d.ts +7 -1
  65. package/lib/engine-components/utils/LookAt.js +43 -6
  66. package/lib/engine-components/utils/LookAt.js.map +1 -1
  67. package/lib/engine-components/webxr/WebXRImageTracking.d.ts +4 -3
  68. package/lib/engine-components/webxr/WebXRImageTracking.js +81 -25
  69. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  70. package/lib/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +1 -1
  72. package/plugins/vite/reload.js +13 -2
  73. package/src/engine/codegen/register_types.js +52 -4
  74. package/src/engine/engine_gameobject.ts +3 -2
  75. package/src/engine/engine_three_utils.ts +2 -2
  76. package/src/engine-components/Animation.ts +4 -0
  77. package/src/engine-components/codegen/components.ts +25 -1
  78. package/src/engine-components/export/usdz/Extension.ts +4 -5
  79. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +1280 -0
  80. package/src/engine-components/export/usdz/USDZExporter.ts +39 -17
  81. package/src/engine-components/export/usdz/extensions/Animation.ts +37 -45
  82. package/src/engine-components/export/usdz/extensions/DocumentExtension.ts +10 -0
  83. package/src/engine-components/export/usdz/extensions/USDZText.ts +142 -0
  84. package/src/engine-components/export/usdz/extensions/behavior/Actions.ts +99 -0
  85. package/src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +181 -0
  86. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +503 -0
  87. package/src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts +459 -0
  88. package/src/engine-components/postprocessing/PostProcessingHandler.ts +1 -1
  89. package/src/engine-components/ui/BaseUIComponent.ts +7 -1
  90. package/src/engine-components/ui/Canvas.ts +80 -5
  91. package/src/engine-components/ui/Graphic.ts +2 -0
  92. package/src/engine-components/ui/Image.ts +3 -3
  93. package/src/engine-components/ui/Interfaces.ts +30 -6
  94. package/src/engine-components/ui/Layout.ts +303 -4
  95. package/src/engine-components/ui/RectTransform.ts +65 -40
  96. package/src/engine-components/utils/LookAt.ts +60 -7
  97. package/src/engine-components/webxr/WebXRImageTracking.ts +100 -27
  98. package/lib/engine-components/export/usdz/types.d.ts +0 -34
  99. package/lib/engine-components/export/usdz/types.js +0 -2
  100. package/lib/engine-components/export/usdz/types.js.map +0 -1
  101. package/src/engine-components/export/usdz/types.ts +0 -39
@@ -0,0 +1,830 @@
1
+ import { PlaneGeometry, Texture, Uniform, PerspectiveCamera, Scene, Mesh, ShaderMaterial, WebGLRenderer, MathUtils, Matrix4, RepeatWrapping, MirroredRepeatWrapping, DoubleSide, } from 'three';
2
+ import * as fflate from 'three/examples/jsm/libs/fflate.module.js';
3
+ function makeNameSafe(str) {
4
+ str = str.replace(/[^a-zA-Z0-9_]/g, '');
5
+ // if str does not start with a-zA-Z_ add _ to the beginning
6
+ if (!str.match(/^[a-zA-Z_]/))
7
+ str = '_' + str;
8
+ return str;
9
+ }
10
+ class USDObject {
11
+ static USDObject_export_id = 0;
12
+ uuid;
13
+ name;
14
+ matrix;
15
+ _isDynamic;
16
+ get isDynamic() { return this._isDynamic; }
17
+ set isDynamic(value) { this._isDynamic = value; }
18
+ geometry;
19
+ material;
20
+ camera;
21
+ parent;
22
+ children = [];
23
+ _eventListeners;
24
+ mesh;
25
+ static createEmptyParent(object) {
26
+ const emptyParent = new USDObject(MathUtils.generateUUID(), object.name + '_empty_' + (USDObject.USDObject_export_id++), object.matrix);
27
+ const parent = object.parent;
28
+ parent.add(emptyParent);
29
+ emptyParent.add(object);
30
+ emptyParent.isDynamic = true;
31
+ object.matrix = new Matrix4().identity();
32
+ return emptyParent;
33
+ }
34
+ constructor(id, name, matrix, mesh = null, material = null, camera = null) {
35
+ this.uuid = id;
36
+ this.name = makeNameSafe(name);
37
+ this.matrix = matrix;
38
+ this.geometry = mesh;
39
+ this.material = material;
40
+ this.camera = camera;
41
+ this.parent = null;
42
+ this.children = [];
43
+ this._eventListeners = {};
44
+ this._isDynamic = false;
45
+ }
46
+ is(obj) {
47
+ if (!obj)
48
+ return false;
49
+ return this.uuid === obj.uuid;
50
+ }
51
+ isEmpty() {
52
+ return !this.geometry;
53
+ }
54
+ clone() {
55
+ const clone = new USDObject(MathUtils.generateUUID(), this.name, this.matrix, this.mesh, this.material);
56
+ clone.isDynamic = this.isDynamic;
57
+ return clone;
58
+ }
59
+ getPath() {
60
+ let current = this.parent;
61
+ let path = this.name;
62
+ while (current) {
63
+ path = current.name + '/' + path;
64
+ current = current.parent;
65
+ }
66
+ return '</' + path + '>';
67
+ }
68
+ add(child) {
69
+ if (child.parent) {
70
+ child.parent.remove(child);
71
+ }
72
+ child.parent = this;
73
+ this.children.push(child);
74
+ }
75
+ remove(child) {
76
+ const index = this.children.indexOf(child);
77
+ if (index >= 0) {
78
+ if (child.parent === this)
79
+ child.parent = null;
80
+ this.children.splice(index, 1);
81
+ }
82
+ }
83
+ addEventListener(evt, listener) {
84
+ if (!this._eventListeners[evt])
85
+ this._eventListeners[evt] = [];
86
+ this._eventListeners[evt].push(listener);
87
+ }
88
+ removeEventListener(evt, listener) {
89
+ if (!this._eventListeners[evt])
90
+ return;
91
+ const index = this._eventListeners[evt].indexOf(listener);
92
+ if (index >= 0) {
93
+ this._eventListeners[evt].splice(index, 1);
94
+ }
95
+ }
96
+ onSerialize(writer, context) {
97
+ const listeners = this._eventListeners['serialize'];
98
+ if (listeners)
99
+ listeners.forEach(listener => listener(writer, context));
100
+ }
101
+ }
102
+ class USDDocument extends USDObject {
103
+ stageLength;
104
+ get isDocumentRoot() {
105
+ return true;
106
+ }
107
+ get isDynamic() {
108
+ return false;
109
+ }
110
+ constructor() {
111
+ super(undefined, 'StageRoot', new Matrix4(), null, null, null);
112
+ this.children = [];
113
+ this.stageLength = 200;
114
+ }
115
+ add(child) {
116
+ child.parent = this;
117
+ this.children.push(child);
118
+ }
119
+ remove(child) {
120
+ const index = this.children.indexOf(child);
121
+ if (index >= 0) {
122
+ if (child.parent === this)
123
+ child.parent = null;
124
+ this.children.splice(index, 1);
125
+ }
126
+ }
127
+ traverse(callback, current = null) {
128
+ if (current !== null)
129
+ callback(current);
130
+ else
131
+ current = this;
132
+ if (current.children) {
133
+ for (const child of current.children) {
134
+ this.traverse(callback, child);
135
+ }
136
+ }
137
+ }
138
+ findById(uuid) {
139
+ let found = false;
140
+ function search(current) {
141
+ if (found)
142
+ return;
143
+ if (current.uuid === uuid) {
144
+ found = true;
145
+ return current;
146
+ }
147
+ if (current.children) {
148
+ for (const child of current.children) {
149
+ const res = search(child);
150
+ if (res)
151
+ return res;
152
+ }
153
+ }
154
+ }
155
+ return search(this);
156
+ }
157
+ buildHeader() {
158
+ return `#usda 1.0
159
+ (
160
+ customLayerData = {
161
+ string creator = "Three.js USDZExporter"
162
+ }
163
+ defaultPrim = "${makeNameSafe(this.name)}"
164
+ metersPerUnit = 1
165
+ upAxis = "Y"
166
+ startTimeCode = 0
167
+ endTimeCode = ${this.stageLength}
168
+ timeCodesPerSecond = 60
169
+ framesPerSecond = 60
170
+ )
171
+ `;
172
+ }
173
+ }
174
+ const newLine = '\n';
175
+ class USDWriter {
176
+ str;
177
+ indent;
178
+ constructor() {
179
+ this.str = '';
180
+ this.indent = 0;
181
+ }
182
+ clear() {
183
+ this.str = '';
184
+ this.indent = 0;
185
+ }
186
+ beginBlock(str) {
187
+ str = this.applyIndent(str);
188
+ this.str += str;
189
+ this.str += newLine;
190
+ this.str += this.applyIndent('{');
191
+ this.str += newLine;
192
+ this.indent += 1;
193
+ }
194
+ closeBlock() {
195
+ this.indent -= 1;
196
+ this.str += this.applyIndent('}') + newLine;
197
+ }
198
+ beginArray(str) {
199
+ str = this.applyIndent(str + ' = [');
200
+ this.str += str;
201
+ this.str += newLine;
202
+ this.indent += 1;
203
+ }
204
+ closeArray() {
205
+ this.indent -= 1;
206
+ this.str += this.applyIndent(']') + newLine;
207
+ }
208
+ appendLine(str = '') {
209
+ str = this.applyIndent(str);
210
+ this.str += str;
211
+ this.str += newLine;
212
+ }
213
+ toString() {
214
+ return this.str;
215
+ }
216
+ applyIndent(str) {
217
+ let indents = '';
218
+ for (let i = 0; i < this.indent; i++)
219
+ indents += '\t';
220
+ return indents + str;
221
+ }
222
+ }
223
+ class USDZExporterContext {
224
+ root;
225
+ exporter;
226
+ extensions;
227
+ materials;
228
+ textures;
229
+ files;
230
+ document;
231
+ output;
232
+ constructor(root, exporter, extensions) {
233
+ this.root = root;
234
+ this.exporter = exporter;
235
+ if (extensions)
236
+ this.extensions = extensions;
237
+ this.materials = {};
238
+ this.textures = {};
239
+ this.files = {};
240
+ this.document = new USDDocument();
241
+ this.output = '';
242
+ }
243
+ }
244
+ class USDZExporterOptions {
245
+ ar = { anchoring: { type: 'plane' } };
246
+ planeAnchoring = { alignment: 'horizontal' };
247
+ extensions = [];
248
+ }
249
+ class USDZExporter {
250
+ debug;
251
+ sceneAnchoringOptions = {};
252
+ extensions;
253
+ constructor() {
254
+ this.debug = false;
255
+ }
256
+ async parse(scene, options = new USDZExporterOptions()) {
257
+ options = Object.assign({
258
+ ar: {
259
+ anchoring: { type: 'plane' },
260
+ planeAnchoring: { alignment: 'horizontal' }
261
+ },
262
+ extensions: []
263
+ }, options);
264
+ this.sceneAnchoringOptions = options;
265
+ // @ts-ignore
266
+ const context = new USDZExporterContext(scene, this, options.extensions);
267
+ this.extensions = context.extensions;
268
+ const files = context.files;
269
+ const modelFileName = 'model.usda';
270
+ // model file should be first in USDZ archive so we init it here
271
+ files[modelFileName] = null;
272
+ const materials = context.materials;
273
+ const textures = context.textures;
274
+ invokeAll(context, 'onBeforeBuildDocument');
275
+ traverseVisible(scene, context.document, context);
276
+ invokeAll(context, 'onAfterBuildDocument');
277
+ parseDocument(context);
278
+ invokeAll(context, 'onAfterSerialize');
279
+ context.output += buildMaterials(materials, textures);
280
+ const header = context.document.buildHeader();
281
+ const final = header + '\n' + context.output;
282
+ // full output file
283
+ if (this.debug)
284
+ console.log(final);
285
+ files[modelFileName] = fflate.strToU8(final);
286
+ context.output = '';
287
+ for (const id in textures) {
288
+ let texture = textures[id];
289
+ const isRGBA = texture.format === 1023;
290
+ if (texture.isCompressedTexture) {
291
+ texture = copyTexture(texture);
292
+ }
293
+ // TODO add readback options for textures that don't have texture.image
294
+ const canvas = await imageToCanvas(texture.image);
295
+ if (canvas) {
296
+ const blob = await new Promise(resolve => canvas.toBlob(resolve, isRGBA ? 'image/png' : 'image/jpeg', 1));
297
+ files[`textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}`] = new Uint8Array(await blob.arrayBuffer());
298
+ }
299
+ else {
300
+ console.warn('Can`t export texture: ', texture);
301
+ }
302
+ }
303
+ // 64 byte alignment
304
+ // https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109
305
+ let offset = 0;
306
+ for (const filename in files) {
307
+ const file = files[filename];
308
+ const headerSize = 34 + filename.length;
309
+ offset += headerSize;
310
+ const offsetMod64 = offset & 63;
311
+ if (offsetMod64 !== 4) {
312
+ const padLength = 64 - offsetMod64;
313
+ const padding = new Uint8Array(padLength);
314
+ files[filename] = [file, { extra: { 12345: padding } }];
315
+ }
316
+ offset = file.length;
317
+ }
318
+ return fflate.zipSync(files, { level: 0 });
319
+ }
320
+ }
321
+ function traverseVisible(object, parentModel, context) {
322
+ if (!object.visible)
323
+ return;
324
+ let model = undefined;
325
+ const geometry = object.geometry;
326
+ const material = object.material;
327
+ if (object.isMesh && material && (material.isMeshStandardMaterial || material.isMeshBasicMaterial) && !object.isSkinnedMesh) {
328
+ const name = getObjectId(object);
329
+ model = new USDObject(object.uuid, name, object.matrix, geometry, material);
330
+ }
331
+ else if (object.isCamera) {
332
+ const name = getObjectId(object);
333
+ model = new USDObject(object.uuid, name, object.matrix, undefined, undefined, object);
334
+ }
335
+ else {
336
+ const name = getObjectId(object);
337
+ model = new USDObject(object.uuid, name, object.matrix);
338
+ }
339
+ if (model) {
340
+ if (parentModel) {
341
+ parentModel.add(model);
342
+ }
343
+ parentModel = model;
344
+ if (context.extensions) {
345
+ for (const ext of context.extensions) {
346
+ if (ext.onExportObject)
347
+ ext.onExportObject.call(ext, object, model, context);
348
+ }
349
+ }
350
+ }
351
+ else {
352
+ const name = getObjectId(object);
353
+ const empty = new USDObject(object.uuid, name, object.matrix);
354
+ if (parentModel) {
355
+ parentModel.add(empty);
356
+ }
357
+ parentModel = empty;
358
+ }
359
+ for (const ch of object.children) {
360
+ traverseVisible(ch, parentModel, context);
361
+ }
362
+ }
363
+ function parseDocument(context) {
364
+ for (const child of context.document.children) {
365
+ addResources(child, context);
366
+ }
367
+ const writer = new USDWriter();
368
+ writer.beginBlock(`def Xform "${context.document.name}"`);
369
+ writer.beginBlock(`def Scope "Scenes" (
370
+ kind = "sceneLibrary"
371
+ )`);
372
+ writer.beginBlock(`def Xform "Scene" (
373
+ apiSchemas = ["Preliminary_AnchoringAPI"]
374
+ customData = {
375
+ bool preliminary_collidesWithEnvironment = 0
376
+ string sceneName = "Scene"
377
+ }
378
+ sceneName = "Scene"
379
+ )`);
380
+ writer.appendLine(`token preliminary:anchoring:type = "${context.exporter.sceneAnchoringOptions.ar.anchoring.type}"`);
381
+ if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'plane')
382
+ writer.appendLine(`token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.ar.planeAnchoring.alignment}"`);
383
+ // bit hacky as we don't have a callback here yet. Relies on the fact that the image is named identical in the ImageTracking extension.
384
+ if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'image')
385
+ writer.appendLine(`rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>`);
386
+ writer.appendLine();
387
+ for (const child of context.document.children) {
388
+ buildXform(child, writer, context);
389
+ }
390
+ invokeAll(context, 'onAfterHierarchy', writer);
391
+ writer.closeBlock();
392
+ writer.closeBlock();
393
+ writer.closeBlock();
394
+ context.output += writer.toString();
395
+ }
396
+ function addResources(object, context) {
397
+ const geometry = object.geometry;
398
+ let material = object.material;
399
+ if (geometry) {
400
+ if (material.isMeshStandardMaterial) { // || material.isMeshBasicMaterial // TODO convert unlit to lit+emissive
401
+ const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usd';
402
+ if (!(geometryFileName in context.files)) {
403
+ const meshObject = buildMeshObject(geometry);
404
+ context.files[geometryFileName] = buildUSDFileAsString(meshObject, context);
405
+ }
406
+ if (!(material.uuid in context.materials)) {
407
+ context.materials[material.uuid] = material;
408
+ }
409
+ }
410
+ else {
411
+ console.warn('THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)', name);
412
+ }
413
+ }
414
+ for (const ch of object.children) {
415
+ addResources(ch, context);
416
+ }
417
+ }
418
+ function invokeAll(context, name, writer = null) {
419
+ if (context.extensions) {
420
+ for (const ext of context.extensions) {
421
+ if (typeof ext[name] === 'function')
422
+ ext[name](context, writer);
423
+ }
424
+ }
425
+ }
426
+ function copyTexture(texture) {
427
+ const geometry = new PlaneGeometry(2, 2, 1, 1);
428
+ const material = new ShaderMaterial({
429
+ uniforms: { blitTexture: new Uniform(texture) },
430
+ vertexShader: `
431
+ varying vec2 vUv;
432
+ void main(){
433
+ vUv = uv;
434
+ gl_Position = vec4(position.xy * 1.0,0.,.999999);
435
+ }`,
436
+ fragmentShader: `
437
+ uniform sampler2D blitTexture;
438
+ varying vec2 vUv;
439
+ void main(){
440
+ gl_FragColor = vec4(vUv.xy, 0, 1);
441
+ gl_FragColor = texture2D( blitTexture, vUv);
442
+ }`
443
+ });
444
+ const mesh = new Mesh(geometry, material);
445
+ mesh.frustumCulled = false;
446
+ const cam = new PerspectiveCamera();
447
+ const scene = new Scene();
448
+ scene.add(mesh);
449
+ const renderer = new WebGLRenderer({ antialias: false });
450
+ renderer.setSize(texture.image.width, texture.image.height);
451
+ renderer.clear();
452
+ renderer.render(scene, cam);
453
+ return new Texture(renderer.domElement);
454
+ }
455
+ function isImageBitmap(image) {
456
+ return (typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement) ||
457
+ (typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement) ||
458
+ (typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas) ||
459
+ (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap);
460
+ }
461
+ async function imageToCanvas(image, color = undefined, flipY = false) {
462
+ if (isImageBitmap(image)) {
463
+ const scale = 1024 / Math.max(image.width, image.height);
464
+ const canvas = document.createElement('canvas');
465
+ canvas.width = image.width * Math.min(1, scale);
466
+ canvas.height = image.height * Math.min(1, scale);
467
+ const context = canvas.getContext('2d');
468
+ if (!context)
469
+ throw new Error('Could not get canvas 2D context');
470
+ if (flipY === true) {
471
+ context.translate(0, canvas.height);
472
+ context.scale(1, -1);
473
+ }
474
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
475
+ // TODO remove, not used anymore
476
+ if (color !== undefined) {
477
+ const hex = parseInt(color, 16);
478
+ const r = (hex >> 16 & 255) / 255;
479
+ const g = (hex >> 8 & 255) / 255;
480
+ const b = (hex & 255) / 255;
481
+ const imagedata = context.getImageData(0, 0, canvas.width, canvas.height);
482
+ const data = imagedata.data;
483
+ for (let i = 0; i < data.length; i += 4) {
484
+ data[i + 0] = data[i + 0] * r;
485
+ data[i + 1] = data[i + 1] * g;
486
+ data[i + 2] = data[i + 2] * b;
487
+ }
488
+ context.putImageData(imagedata, 0, 0);
489
+ }
490
+ return canvas;
491
+ }
492
+ else {
493
+ throw new Error('THREE.USDZExporter: No valid image data found. Unable to process texture.');
494
+ }
495
+ }
496
+ //
497
+ const PRECISION = 7;
498
+ function buildHeader() {
499
+ return `#usda 1.0
500
+ (
501
+ customLayerData = {
502
+ string creator = "Three.js USDZExporter"
503
+ }
504
+ metersPerUnit = 1
505
+ upAxis = "Y"
506
+ )
507
+ `;
508
+ }
509
+ function buildUSDFileAsString(dataToInsert, _context) {
510
+ let output = buildHeader();
511
+ output += dataToInsert;
512
+ return fflate.strToU8(output);
513
+ }
514
+ function getObjectId(object) {
515
+ return object.name.replace(/[-<>\(\)\[\]§$%&\/\\\=\?\,\;]/g, '') + '_' + object.id;
516
+ }
517
+ // Xform
518
+ export function buildXform(model, writer, context) {
519
+ const matrix = model.matrix;
520
+ const geometry = model.geometry;
521
+ const material = model.material;
522
+ const camera = model.camera;
523
+ const name = model.name;
524
+ const transform = buildMatrix(matrix);
525
+ if (matrix.determinant() < 0) {
526
+ console.warn('THREE.USDZExporter: USDZ does not support negative scales', name);
527
+ }
528
+ if (geometry)
529
+ writer.beginBlock(`def Xform "${name}" (prepend references = @./geometries/Geometry_${geometry.id}.usd@</Geometry>)`);
530
+ else if (camera)
531
+ writer.beginBlock(`def Camera "${name}"`);
532
+ else
533
+ writer.beginBlock(`def Xform "${name}"`);
534
+ if (material)
535
+ writer.appendLine(`rel material:binding = </Materials/Material_${material.id}>`);
536
+ writer.appendLine(`matrix4d xformOp:transform = ${transform}`);
537
+ writer.appendLine('uniform token[] xformOpOrder = ["xformOp:transform"]');
538
+ if (camera) {
539
+ if (camera.isOrthographicCamera) {
540
+ writer.appendLine(`float2 clippingRange = (${camera.near}, ${camera.far})`);
541
+ writer.appendLine(`float horizontalAperture = ${((Math.abs(camera.left) + Math.abs(camera.right)) * 10).toPrecision(PRECISION)}`);
542
+ writer.appendLine(`float verticalAperture = ${((Math.abs(camera.top) + Math.abs(camera.bottom)) * 10).toPrecision(PRECISION)}`);
543
+ writer.appendLine('token projection = "orthographic"');
544
+ }
545
+ else {
546
+ writer.appendLine(`float2 clippingRange = (${camera.near.toPrecision(PRECISION)}, ${camera.far.toPrecision(PRECISION)})`);
547
+ writer.appendLine(`float focalLength = ${camera.getFocalLength().toPrecision(PRECISION)}`);
548
+ writer.appendLine(`float focusDistance = ${camera.focus.toPrecision(PRECISION)}`);
549
+ writer.appendLine(`float horizontalAperture = ${camera.getFilmWidth().toPrecision(PRECISION)}`);
550
+ writer.appendLine('token projection = "perspective"');
551
+ writer.appendLine(`float verticalAperture = ${camera.getFilmHeight().toPrecision(PRECISION)}`);
552
+ }
553
+ }
554
+ if (model.onSerialize) {
555
+ model.onSerialize(writer, context);
556
+ }
557
+ if (model.children) {
558
+ writer.appendLine();
559
+ for (const ch of model.children) {
560
+ buildXform(ch, writer, context);
561
+ }
562
+ }
563
+ writer.closeBlock();
564
+ }
565
+ function fn(num) {
566
+ return num.toFixed(10);
567
+ }
568
+ function buildMatrix(matrix) {
569
+ const array = matrix.elements;
570
+ return `( ${buildMatrixRow(array, 0)}, ${buildMatrixRow(array, 4)}, ${buildMatrixRow(array, 8)}, ${buildMatrixRow(array, 12)} )`;
571
+ }
572
+ function buildMatrixRow(array, offset) {
573
+ return `(${fn(array[offset + 0])}, ${fn(array[offset + 1])}, ${fn(array[offset + 2])}, ${fn(array[offset + 3])})`;
574
+ }
575
+ // Mesh
576
+ function buildMeshObject(geometry) {
577
+ const mesh = buildMesh(geometry);
578
+ return `
579
+ def "Geometry"
580
+ {
581
+ ${mesh}
582
+ }
583
+ `;
584
+ }
585
+ function buildMesh(geometry) {
586
+ const name = 'Geometry';
587
+ const attributes = geometry.attributes;
588
+ const count = attributes.position.count;
589
+ return `
590
+ def Mesh "${name}"
591
+ {
592
+ int[] faceVertexCounts = [${buildMeshVertexCount(geometry)}]
593
+ int[] faceVertexIndices = [${buildMeshVertexIndices(geometry)}]
594
+ normal3f[] normals = [${buildVector3Array(attributes.normal, count)}] (
595
+ interpolation = "vertex"
596
+ )
597
+ point3f[] points = [${buildVector3Array(attributes.position, count)}]
598
+ ${attributes.uv ?
599
+ `float2[] primvars:st = [${buildVector2Array(attributes.uv, count)}] (
600
+ interpolation = "vertex"
601
+ )` : ''}
602
+ ${attributes.uv2 ?
603
+ `float2[] primvars:st2 = [${buildVector2Array(attributes.uv2, count)}] (
604
+ interpolation = "vertex"
605
+ )` : ''}
606
+ uniform token subdivisionScheme = "none"
607
+ }
608
+ `;
609
+ }
610
+ function buildMeshVertexCount(geometry) {
611
+ const count = geometry.index !== null ? geometry.index.count : geometry.attributes.position.count;
612
+ return Array(count / 3).fill(3).join(', ');
613
+ }
614
+ function buildMeshVertexIndices(geometry) {
615
+ const index = geometry.index;
616
+ const array = [];
617
+ if (index !== null) {
618
+ for (let i = 0; i < index.count; i++) {
619
+ array.push(index.getX(i));
620
+ }
621
+ }
622
+ else {
623
+ const length = geometry.attributes.position.count;
624
+ for (let i = 0; i < length; i++) {
625
+ array.push(i);
626
+ }
627
+ }
628
+ return array.join(', ');
629
+ }
630
+ function buildVector3Array(attribute, count) {
631
+ if (attribute === undefined) {
632
+ console.warn('USDZExporter: Normals missing.');
633
+ return Array(count).fill('(0, 0, 0)').join(', ');
634
+ }
635
+ const array = [];
636
+ for (let i = 0; i < attribute.count; i++) {
637
+ const x = attribute.getX(i);
638
+ const y = attribute.getY(i);
639
+ const z = attribute.getZ(i);
640
+ array.push(`(${x.toPrecision(PRECISION)}, ${y.toPrecision(PRECISION)}, ${z.toPrecision(PRECISION)})`);
641
+ }
642
+ return array.join(', ');
643
+ }
644
+ function buildVector2Array(attribute, count) {
645
+ if (attribute === undefined) {
646
+ console.warn('USDZExporter: UVs missing.');
647
+ return Array(count).fill('(0, 0)').join(', ');
648
+ }
649
+ const array = [];
650
+ for (let i = 0; i < attribute.count; i++) {
651
+ const x = attribute.getX(i);
652
+ const y = attribute.getY(i);
653
+ array.push(`(${x.toPrecision(PRECISION)}, ${1 - y.toPrecision(PRECISION)})`);
654
+ }
655
+ return array.join(', ');
656
+ }
657
+ // Materials
658
+ function buildMaterials(materials, textures) {
659
+ const array = [];
660
+ for (const uuid in materials) {
661
+ const material = materials[uuid];
662
+ array.push(buildMaterial(material, textures));
663
+ }
664
+ return `def "Materials"
665
+ {
666
+ ${array.join('')}
667
+ }
668
+
669
+ `;
670
+ }
671
+ function buildMaterial(material, textures) {
672
+ // https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html
673
+ const pad = ' ';
674
+ const inputs = [];
675
+ const samplers = [];
676
+ const exportForQuickLook = true;
677
+ function buildTexture(texture, mapType, color = undefined, opacity = undefined) {
678
+ const id = texture.id + (color ? '_' + color.getHexString() : '') + (opacity ? '_' + opacity : '');
679
+ const isRGBA = texture.format === 1023;
680
+ const wrapS = (texture.wrapS == RepeatWrapping) ? 'repeat' : (texture.wrapS == MirroredRepeatWrapping ? 'mirror' : 'clamp');
681
+ const wrapT = (texture.wrapT == RepeatWrapping) ? 'repeat' : (texture.wrapT == MirroredRepeatWrapping ? 'mirror' : 'clamp');
682
+ const repeat = texture.repeat.clone();
683
+ const offset = texture.offset.clone();
684
+ // texture coordinates start in the opposite corner, need to correct
685
+ offset.y = 1 - offset.y - repeat.y;
686
+ // turns out QuickLook is buggy and interprets texture repeat inverted.
687
+ // Apple Feedback: FB10036297 and FB11442287
688
+ if (exportForQuickLook) {
689
+ offset.x = offset.x / repeat.x;
690
+ offset.y = offset.y / repeat.y;
691
+ }
692
+ textures[id] = texture;
693
+ const uvReader = mapType == 'occlusion' ? 'uvReader_st2' : 'uvReader_st';
694
+ const needsTextureTransform = (repeat.x != 1 || repeat.y != 1 || offset.x != 0 || offset.y != 0);
695
+ const textureTransformInput = `</Materials/Material_${material.id}/${uvReader}.outputs:result>`;
696
+ const textureTransformOutput = `</Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`;
697
+ return `
698
+ ${needsTextureTransform ? `def Shader "Transform2d_${mapType}" (
699
+ sdrMetadata = {
700
+ string role = "math"
701
+ }
702
+ )
703
+ {
704
+ uniform token info:id = "UsdTransform2d"
705
+ float2 inputs:in.connect = ${textureTransformInput}
706
+ float2 inputs:scale = ${buildVector2(repeat)}
707
+ float2 inputs:translation = ${buildVector2(offset)}
708
+ float2 outputs:result
709
+ }
710
+ ` : ''}
711
+ def Shader "Texture_${texture.id}_${mapType}"
712
+ {
713
+ uniform token info:id = "UsdUVTexture"
714
+ asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@
715
+ float2 inputs:st.connect = ${needsTextureTransform ? textureTransformOutput : textureTransformInput}
716
+ float4 inputs:scale = (${color ? color.r + ', ' + color.g + ', ' + color.b : '1, 1, 1'}, ${opacity ? opacity : '1'})
717
+ token inputs:wrapS = "${wrapS}"
718
+ token inputs:wrapT = "${wrapT}"
719
+ float outputs:r
720
+ float outputs:g
721
+ float outputs:b
722
+ float3 outputs:rgb
723
+ ${material.transparent || material.alphaTest > 0.0 ? 'float outputs:a' : ''}
724
+ }`;
725
+ }
726
+ const effectiveOpacity = (material.transparent || material.alphaTest) ? material.opacity : 1;
727
+ if (material.side === DoubleSide) {
728
+ console.warn('THREE.USDZExporter: USDZ does not support double sided materials', material);
729
+ }
730
+ if (material.map !== null) {
731
+ inputs.push(`${pad}color3f inputs:diffuseColor.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:rgb>`);
732
+ if (material.transparent) {
733
+ inputs.push(`${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:a>`);
734
+ }
735
+ else if (material.alphaTest > 0.0) {
736
+ inputs.push(`${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:a>`);
737
+ inputs.push(`${pad}float inputs:opacityThreshold = ${material.alphaTest}`);
738
+ }
739
+ samplers.push(buildTexture(material.map, 'diffuse', material.color, effectiveOpacity));
740
+ }
741
+ else {
742
+ inputs.push(`${pad}color3f inputs:diffuseColor = ${buildColor(material.color)}`);
743
+ }
744
+ if (material.emissiveMap) {
745
+ inputs.push(`${pad}color3f inputs:emissiveColor.connect = </Materials/Material_${material.id}/Texture_${material.emissiveMap.id}_emissive.outputs:rgb>`);
746
+ samplers.push(buildTexture(material.emissiveMap, 'emissive'));
747
+ }
748
+ else if (material.emissive?.getHex() > 0) {
749
+ inputs.push(`${pad}color3f inputs:emissiveColor = ${buildColor(material.emissive)}`);
750
+ }
751
+ else {
752
+ inputs.push(`${pad}color3f inputs:emissiveColor = (0, 0, 0)`);
753
+ }
754
+ if (material.normalMap) {
755
+ inputs.push(`${pad}normal3f inputs:normal.connect = </Materials/Material_${material.id}/Texture_${material.normalMap.id}_normal.outputs:rgb>`);
756
+ samplers.push(buildTexture(material.normalMap, 'normal'));
757
+ }
758
+ if (material.aoMap) {
759
+ inputs.push(`${pad}float inputs:occlusion.connect = </Materials/Material_${material.id}/Texture_${material.aoMap.id}_occlusion.outputs:r>`);
760
+ samplers.push(buildTexture(material.aoMap, 'occlusion'));
761
+ }
762
+ if (material.roughnessMap && material.roughness === 1) {
763
+ inputs.push(`${pad}float inputs:roughness.connect = </Materials/Material_${material.id}/Texture_${material.roughnessMap.id}_roughness.outputs:g>`);
764
+ samplers.push(buildTexture(material.roughnessMap, 'roughness'));
765
+ }
766
+ else {
767
+ inputs.push(`${pad}float inputs:roughness = ${material.roughness}`);
768
+ }
769
+ if (material.metalnessMap && material.metalness === 1) {
770
+ inputs.push(`${pad}float inputs:metallic.connect = </Materials/Material_${material.id}/Texture_${material.metalnessMap.id}_metallic.outputs:b>`);
771
+ samplers.push(buildTexture(material.metalnessMap, 'metallic'));
772
+ }
773
+ else {
774
+ inputs.push(`${pad}float inputs:metallic = ${material.metalness}`);
775
+ }
776
+ if (material.alphaMap) {
777
+ inputs.push(`${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.alphaMap.id}_opacity.outputs:r>`);
778
+ inputs.push(`${pad}float inputs:opacityThreshold = 0.0001`);
779
+ samplers.push(buildTexture(material.alphaMap, 'opacity'));
780
+ }
781
+ else {
782
+ inputs.push(`${pad}float inputs:opacity = ${effectiveOpacity}`);
783
+ }
784
+ if (material.isMeshPhysicalMaterial) {
785
+ inputs.push(`${pad}float inputs:clearcoat = ${material.clearcoat}`);
786
+ inputs.push(`${pad}float inputs:clearcoatRoughness = ${material.clearcoatRoughness}`);
787
+ inputs.push(`${pad}float inputs:ior = ${material.ior}`);
788
+ }
789
+ return `
790
+ def Material "Material_${material.id}"
791
+ {
792
+ def Shader "PreviewSurface"
793
+ {
794
+ uniform token info:id = "UsdPreviewSurface"
795
+ ${inputs.join('\n')}
796
+ int inputs:useSpecularWorkflow = 0
797
+ token outputs:surface
798
+ }
799
+
800
+ token outputs:surface.connect = </Materials/Material_${material.id}/PreviewSurface.outputs:surface>
801
+
802
+ def Shader "uvReader_st"
803
+ {
804
+ uniform token info:id = "UsdPrimvarReader_float2"
805
+ token inputs:varname = "st"
806
+ float2 inputs:fallback = (0.0, 0.0)
807
+ float2 outputs:result
808
+ }
809
+
810
+ def Shader "uvReader_st2"
811
+ {
812
+ uniform token info:id = "UsdPrimvarReader_float2"
813
+ token inputs:varname = "st2"
814
+ float2 inputs:fallback = (0.0, 0.0)
815
+ float2 outputs:result
816
+ }
817
+
818
+ ${samplers.join('\n')}
819
+
820
+ }
821
+ `;
822
+ }
823
+ function buildColor(color) {
824
+ return `(${color.r}, ${color.g}, ${color.b})`;
825
+ }
826
+ function buildVector2(vector) {
827
+ return `(${vector.x}, ${vector.y})`;
828
+ }
829
+ export { USDZExporter, USDZExporterContext, USDWriter, USDObject, buildMatrix, USDDocument, makeNameSafe as makeNameSafeForUSD, imageToCanvas };
830
+ //# sourceMappingURL=ThreeUSDZExporter.js.map