@needle-tools/engine 4.11.0-beta.1 → 4.11.0-next.687516a

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 (99) hide show
  1. package/components.needle.json +1 -1
  2. package/dist/{gltf-progressive-CXVECA3a.js → gltf-progressive-BvlZQAkt.js} +3 -3
  3. package/dist/{gltf-progressive-D4Z_Khp3.min.js → gltf-progressive-CftVUJy3.min.js} +1 -1
  4. package/dist/{gltf-progressive-CHeORqEv.umd.cjs → gltf-progressive-GwdQV1Qx.umd.cjs} +1 -1
  5. package/dist/{needle-engine.bundle-CFc4UIqz.umd.cjs → needle-engine.bundle-COautGiS.umd.cjs} +142 -142
  6. package/dist/{needle-engine.bundle-6j5gE-aQ.min.js → needle-engine.bundle-RxTD8sUI.min.js} +142 -142
  7. package/dist/{needle-engine.bundle-BDZ09xyt.js → needle-engine.bundle-vaRDfJpp.js} +6211 -6145
  8. package/dist/needle-engine.d.ts +6 -0
  9. package/dist/needle-engine.js +4 -4
  10. package/dist/needle-engine.min.js +1 -1
  11. package/dist/needle-engine.umd.cjs +1 -1
  12. package/dist/{postprocessing-DQ2pynXW.js → postprocessing-CJC0Npcd.js} +2 -2
  13. package/dist/{postprocessing-BsnRNRRS.umd.cjs → postprocessing-DrM4PWU3.umd.cjs} +1 -1
  14. package/dist/{postprocessing-BHMVuZQ1.min.js → postprocessing-l7zsdO_Q.min.js} +1 -1
  15. package/dist/{three-qw28ZtTy.min.js → three-BDW9I486.min.js} +13 -13
  16. package/dist/{three-CJSAehtG.js → three-MHVqtJYj.js} +1 -0
  17. package/dist/{three-examples-Doq0rvFU.js → three-examples-C5Ht-QFN.js} +1 -1
  18. package/dist/{three-examples-Deqc1bNw.umd.cjs → three-examples-CgwGHSgz.umd.cjs} +1 -1
  19. package/dist/{three-examples-BivkhnvN.min.js → three-examples-fvEPSC8L.min.js} +1 -1
  20. package/dist/{three-B-jwTHao.umd.cjs → three-iFaDq9U3.umd.cjs} +13 -13
  21. package/dist/{three-mesh-ui-CktOi6oI.js → three-mesh-ui-BjWTTk1R.js} +1 -1
  22. package/dist/{three-mesh-ui-CsHwj9cJ.umd.cjs → three-mesh-ui-Bm32sS2a.umd.cjs} +1 -1
  23. package/dist/{three-mesh-ui-DhYXcXZe.min.js → three-mesh-ui-CLdkp21K.min.js} +1 -1
  24. package/dist/{vendor-BcsPRUmt.umd.cjs → vendor-C-oI7A4Z.umd.cjs} +2 -2
  25. package/dist/{vendor-CyfN5nor.js → vendor-D4AePCCt.js} +609 -599
  26. package/dist/{vendor-DyavoogU.min.js → vendor-DlEKkX1e.min.js} +25 -25
  27. package/lib/engine/js-extensions/Object3D.d.ts +6 -0
  28. package/lib/engine/js-extensions/Object3D.js +15 -0
  29. package/lib/engine/js-extensions/Object3D.js.map +1 -1
  30. package/lib/engine-components/Collider.d.ts +26 -0
  31. package/lib/engine-components/Collider.js +26 -0
  32. package/lib/engine-components/Collider.js.map +1 -1
  33. package/lib/engine-components/ContactShadows.d.ts +11 -2
  34. package/lib/engine-components/ContactShadows.js +11 -2
  35. package/lib/engine-components/ContactShadows.js.map +1 -1
  36. package/lib/engine-components/DropListener.d.ts +3 -0
  37. package/lib/engine-components/DropListener.js +40 -19
  38. package/lib/engine-components/DropListener.js.map +1 -1
  39. package/lib/engine-components/Duplicatable.d.ts +2 -2
  40. package/lib/engine-components/Duplicatable.js +2 -2
  41. package/lib/engine-components/EventList.d.ts +18 -1
  42. package/lib/engine-components/EventList.js +18 -1
  43. package/lib/engine-components/EventList.js.map +1 -1
  44. package/lib/engine-components/GroundProjection.d.ts +3 -0
  45. package/lib/engine-components/GroundProjection.js +3 -0
  46. package/lib/engine-components/GroundProjection.js.map +1 -1
  47. package/lib/engine-components/Interactable.d.ts +4 -0
  48. package/lib/engine-components/Interactable.js +4 -0
  49. package/lib/engine-components/Interactable.js.map +1 -1
  50. package/lib/engine-components/OrbitControls.d.ts +4 -2
  51. package/lib/engine-components/OrbitControls.js +31 -3
  52. package/lib/engine-components/OrbitControls.js.map +1 -1
  53. package/lib/engine-components/RigidBody.d.ts +5 -0
  54. package/lib/engine-components/RigidBody.js +5 -0
  55. package/lib/engine-components/RigidBody.js.map +1 -1
  56. package/lib/engine-components/SeeThrough.js +20 -0
  57. package/lib/engine-components/SeeThrough.js.map +1 -1
  58. package/lib/engine-components/export/usdz/ThreeUSDZExporter.d.ts +4 -2
  59. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +69 -14
  60. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
  61. package/lib/engine-components/ui/Text.js +6 -1
  62. package/lib/engine-components/ui/Text.js.map +1 -1
  63. package/lib/engine-components/utils/LookAt.d.ts +3 -0
  64. package/lib/engine-components/utils/LookAt.js +3 -0
  65. package/lib/engine-components/utils/LookAt.js.map +1 -1
  66. package/lib/engine-components/utils/OpenURL.d.ts +2 -1
  67. package/lib/engine-components/utils/OpenURL.js +2 -1
  68. package/lib/engine-components/utils/OpenURL.js.map +1 -1
  69. package/lib/engine-components/webxr/WebARCameraBackground.d.ts +2 -0
  70. package/lib/engine-components/webxr/WebARCameraBackground.js +2 -0
  71. package/lib/engine-components/webxr/WebARCameraBackground.js.map +1 -1
  72. package/lib/engine-components/webxr/WebARSessionRoot.d.ts +1 -1
  73. package/lib/engine-components/webxr/WebARSessionRoot.js +1 -1
  74. package/lib/engine-components/webxr/WebXR.d.ts +2 -0
  75. package/lib/engine-components/webxr/WebXR.js +2 -0
  76. package/lib/engine-components/webxr/WebXR.js.map +1 -1
  77. package/lib/engine-components/webxr/WebXRImageTracking.d.ts +29 -0
  78. package/lib/engine-components/webxr/WebXRImageTracking.js +29 -0
  79. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  80. package/package.json +3 -3
  81. package/src/engine/js-extensions/Object3D.ts +24 -0
  82. package/src/engine-components/Collider.ts +27 -1
  83. package/src/engine-components/ContactShadows.ts +12 -4
  84. package/src/engine-components/DropListener.ts +43 -23
  85. package/src/engine-components/Duplicatable.ts +2 -2
  86. package/src/engine-components/EventList.ts +18 -1
  87. package/src/engine-components/GroundProjection.ts +4 -1
  88. package/src/engine-components/Interactable.ts +4 -1
  89. package/src/engine-components/OrbitControls.ts +27 -3
  90. package/src/engine-components/RigidBody.ts +6 -1
  91. package/src/engine-components/SeeThrough.ts +42 -2
  92. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +117 -17
  93. package/src/engine-components/ui/Text.ts +11 -2
  94. package/src/engine-components/utils/LookAt.ts +3 -0
  95. package/src/engine-components/utils/OpenURL.ts +3 -2
  96. package/src/engine-components/webxr/WebARCameraBackground.ts +2 -0
  97. package/src/engine-components/webxr/WebARSessionRoot.ts +1 -1
  98. package/src/engine-components/webxr/WebXR.ts +2 -0
  99. package/src/engine-components/webxr/WebXRImageTracking.ts +30 -3
@@ -1,23 +1,22 @@
1
- import { AxesHelper, Box3, Cache, Object3D, Vector2, Vector3 } from "three";
1
+ import { Box3, Object3D, Vector2, Vector3 } from "three";
2
2
 
3
3
  import { isDevEnvironment } from "../engine/debug/index.js";
4
4
  import { AnimationUtils } from "../engine/engine_animation.js";
5
- import { addComponent } from "../engine/engine_components.js";
6
5
  import { Context } from "../engine/engine_context.js";
7
6
  import { destroy } from "../engine/engine_gameobject.js";
8
7
  import { Gizmos } from "../engine/engine_gizmos.js";
9
8
  import { getLoader } from "../engine/engine_gltf.js";
10
9
  import { BlobStorage } from "../engine/engine_networking_blob.js";
11
10
  import { PreviewHelper } from "../engine/engine_networking_files.js";
12
- import { generateSeed, InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
11
+ import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
13
12
  import { serializable } from "../engine/engine_serialization_decorator.js";
14
13
  import { fitObjectIntoVolume, getBoundingBox, placeOnSurface } from "../engine/engine_three_utils.js";
15
- import { IGameObject, Model, Vec3 } from "../engine/engine_types.js";
14
+ import { Model, Vec3 } from "../engine/engine_types.js";
16
15
  import { getParam, setParamWithoutReload } from "../engine/engine_utils.js";
17
16
  import { determineMimeTypeFromExtension } from "../engine/engine_utils_format.js";
18
- import { Animation } from "./Animation.js";
19
17
  import { Behaviour } from "./Component.js";
20
18
  import { EventList } from "./EventList.js";
19
+ import { Renderer } from "./Renderer.js";
21
20
 
22
21
  /**
23
22
  * Debug mode can be enabled with the URL parameter `?debugdroplistener`, which
@@ -116,6 +115,8 @@ const blobKeyName = "blob";
116
115
  * If {@link useNetworking} is enabled, the DropListener will automatically synchronize dropped files to other connected clients.
117
116
  * Enable {@link fitIntoVolume} to automatically scale dropped objects to fit within the volume defined by {@link fitVolumeSize}.
118
117
  *
118
+ * - Example: [DropListener Sample](https://droplistener-zubcksz1veaoo.needle.run/)
119
+ *
119
120
  * The following events are dispatched by the DropListener:
120
121
  * - **object-added** - dispatched when a new object is added to the scene
121
122
  * - **file-dropped** - dispatched when a file is dropped into the scene
@@ -214,7 +215,14 @@ export class DropListener extends Behaviour {
214
215
  }
215
216
 
216
217
 
217
-
218
+ awake() {
219
+ for (const ch of this.gameObject.children) {
220
+ if (this.dropArea && ch.contains(this.dropArea)) {
221
+ continue;
222
+ }
223
+ this._addedObjects.push(ch);
224
+ }
225
+ }
218
226
 
219
227
  // #region internals
220
228
 
@@ -223,7 +231,15 @@ export class DropListener extends Behaviour {
223
231
  this.context.renderer.domElement.addEventListener("dragover", this.onDrag);
224
232
  this.context.renderer.domElement.addEventListener("drop", this.onDrop);
225
233
  window.addEventListener("paste", this.handlePaste);
226
- this.context.connection.beginListen("droplistener", this.onNetworkEvent)
234
+ this.context.connection.beginListen("droplistener", this.onNetworkEvent);
235
+ if (isDevEnvironment()) {
236
+ if (this.dropArea) {
237
+ const anyRenderer = this.dropArea.getComponentInChildren(Renderer);
238
+ if (!anyRenderer) {
239
+ console.warn("[DropListener] The assigned DropArea does not seem to have a renderer/mesh. Drag and Drop events will not be detected.");
240
+ }
241
+ }
242
+ }
227
243
  }
228
244
  /** @internal */
229
245
  onDisable(): void {
@@ -473,7 +489,7 @@ export class DropListener extends Behaviour {
473
489
  if (doDestroy) {
474
490
  for (const prev of this._addedObjects) {
475
491
  if (prev.parent === this.gameObject) {
476
- destroy(prev, true, true);
492
+ prev.destroy();
477
493
  }
478
494
  }
479
495
  }
@@ -596,24 +612,28 @@ export class DropListener extends Behaviour {
596
612
  * @returns True if the drop is valid (either no drop area is set or the drop occurred inside it)
597
613
  */
598
614
  private testIfIsInDropArea(ctx: DropContext): boolean {
615
+ const screenPoint = this.context.input.convertScreenspaceToRaycastSpace(ctx.screenposition.clone());
616
+ const hits = this.context.physics.raycast({
617
+ screenPoint,
618
+ recursive: true,
619
+ testObject: obj => {
620
+ // Ignore hits on the already added objects, they don't count as part of the dropzone
621
+ if (this._addedObjects.some(o => o.contains(obj))) return false;
622
+ return true;
623
+ }
624
+ });
625
+ if (!hits.length) {
626
+ if (isDevEnvironment()) console.log(`Dropped outside of drop area for DropListener \"${this.name}\".`);
627
+ return false;
628
+ }
629
+
630
+ const hit = hits[0];
599
631
  if (this.dropArea) {
600
- const screenPoint = this.context.input.convertScreenspaceToRaycastSpace(ctx.screenposition.clone());
601
- const hits = this.context.physics.raycast({
602
- targets: [this.dropArea],
603
- screenPoint,
604
- recursive: true,
605
- testObject: obj => {
606
- // Ignore hits on the already added objects, they don't count as part of the dropzone
607
- if (this._addedObjects.includes(obj)) return false;
608
- return true;
609
- }
610
- });
611
- if (!hits.length) {
612
- if (isDevEnvironment()) console.log(`Dropped outside of drop area for DropListener \"${this.name}\".`);
613
- return false;
632
+ if (this.dropArea.contains(hit.object)) {
633
+ return true;
614
634
  }
615
635
  }
616
- return true;
636
+ return false;
617
637
  }
618
638
 
619
639
  }
@@ -12,8 +12,8 @@ import { type IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.
12
12
  import { ObjectRaycaster } from "./ui/Raycaster.js";
13
13
 
14
14
  /**
15
- * The Duplicatable component is used to duplicate a assigned {@link GameObject} when a pointer event occurs on the GameObject.
16
- * It implements the {@link IPointerEventHandler} interface and can be used to expose duplication to the user in the editor without writing code.
15
+ * The Duplicatable component is used to duplicate a assigned {@link GameObject} when a pointer event occurs on the object.
16
+ *
17
17
  * @category Interactivity
18
18
  * @group Components
19
19
  */
@@ -97,7 +97,24 @@ export class EventListEvent<TArgs extends any> extends Event { //implements Arra
97
97
 
98
98
 
99
99
  /**
100
- * The EventList is a class that can be used to create a list of event listeners that can be invoked
100
+ * The EventList is a class that can be used to create a list of event listeners that can be invoked.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * // create an event list
105
+ * const onClick = new EventList<(event: MouseEvent) => void>();
106
+ *
107
+ * // add an event listener
108
+ * onClick.addEventListener((event) => {
109
+ * console.log("Clicked!", event);
110
+ * });
111
+ *
112
+ * // invoke the event list
113
+ * onClick.invoke(new MouseEvent("click"));
114
+ * ```
115
+ *
116
+ * @category Events
117
+ * @group Utilities
101
118
  */
102
119
  export class EventList<TArgs extends any = any> implements IEventList {
103
120
 
@@ -10,7 +10,10 @@ import { Behaviour } from "./Component.js";
10
10
  const debug = getParam("debuggroundprojection");
11
11
 
12
12
  /**
13
- * GroundProjectedEnv creates a ground projection of the current environment map.
13
+ * GroundProjectedEnv creates a ground projection of the current environment map.
14
+ *
15
+ * - Example https://engine.needle.tools/samples/ground-projection
16
+ *
14
17
  * @category Rendering
15
18
  * @group Components
16
19
  */
@@ -10,6 +10,9 @@ export class UsageMarker extends Behaviour
10
10
  public usedBy: any = null;
11
11
  }
12
12
 
13
-
13
+ /**
14
+ * An empty component that can be used to mark an object as interactable.
15
+ * @group Components
16
+ */
14
17
  /** @deprecated */
15
18
  export class Interactable extends Behaviour {}
@@ -255,7 +255,12 @@ export class OrbitControls extends Behaviour implements ICameraController {
255
255
  * @default 1
256
256
  */
257
257
  @serializable()
258
- targetLerpDuration = 1;
258
+ get targetLerpDuration() { return this._lookTargetLerpDuration; }
259
+ set targetLerpDuration(v) { this._lookTargetLerpDuration = v; }
260
+ private _lookTargetLerpDuration: number = 1;
261
+
262
+ @serializable(Object3D)
263
+ targetBounds: Object3D | null = null;
259
264
 
260
265
  /**
261
266
  * Rotate the camera left (or right) by the specified angle in radians.
@@ -326,7 +331,6 @@ export class OrbitControls extends Behaviour implements ICameraController {
326
331
  private _lookTargetStartPosition: Vector3 = new Vector3();
327
332
  private _lookTargetEndPosition: Vector3 = new Vector3();
328
333
  private _lookTargetLerp01: number = 0;
329
- private _lookTargetLerpDuration: number = 0;
330
334
 
331
335
  private _cameraLerpActive: boolean = false;
332
336
  private _cameraStartPosition: Vector3 = new Vector3();
@@ -685,6 +689,26 @@ export class OrbitControls extends Behaviour implements ICameraController {
685
689
  }
686
690
  }
687
691
 
692
+ if (this.targetBounds) {
693
+ // #region target bounds
694
+ const targetVector = this._controls.target;
695
+ const boundsCenter = this.targetBounds.worldPosition;
696
+ const boundsHalfSize = getTempVector(this.targetBounds.worldScale).multiplyScalar(0.5);
697
+ const min = getTempVector(boundsCenter).sub(boundsHalfSize);
698
+ const max = getTempVector(boundsCenter).add(boundsHalfSize);
699
+ const newTarget = getTempVector(this._controls.target).clamp(min, max);
700
+ const duration = .1;
701
+ if (duration <= 0) targetVector.copy(newTarget);
702
+ else targetVector.lerp(newTarget, this.context.time.deltaTime / duration);
703
+ if (this._lookTargetLerpActive) {
704
+ if (duration <= 0) this._lookTargetEndPosition.copy(newTarget);
705
+ else this._lookTargetEndPosition.lerp(newTarget, this.context.time.deltaTime / (duration * 5));
706
+ }
707
+ if (debug) {
708
+ Gizmos.DrawWireBox(boundsCenter, boundsHalfSize.multiplyScalar(2), 0xffaa00);
709
+ }
710
+ }
711
+
688
712
 
689
713
  if (this._controls) {
690
714
  if (this.debugLog)
@@ -1000,7 +1024,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
1000
1024
  */
1001
1025
  fitCamera(options?: OrbitFitCameraOptions);
1002
1026
  /** @deprecated Use fitCamera(options) */
1003
- fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<OrbitFitCameraOptions, "objects">);
1027
+ fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<OrbitFitCameraOptions, "objects">);
1004
1028
  fitCamera(objectsOrOptions?: Object3D | Array<Object3D> | OrbitFitCameraOptions, options?: OrbitFitCameraOptions): void {
1005
1029
 
1006
1030
 
@@ -134,7 +134,12 @@ class TransformWatch {
134
134
  }
135
135
 
136
136
  /**
137
- * A Rigidbody is used together with a Collider to create physical interactions between objects in the scene.
137
+ * A Rigidbody is used together with a Collider to create physical interactions between objects in the scene.
138
+ *
139
+ * - Example: https://samples.needle.tools/physics-basic
140
+ * - Example: https://samples.needle.tools/physics-playground
141
+ * - Example: https://samples.needle.tools/physics-&-animation
142
+ *
138
143
  * @category Physics
139
144
  * @group Components
140
145
  */
@@ -1,11 +1,14 @@
1
- import { Material, Object3D, Vector3 } from "three";
1
+ import { Material, Object3D, Object3DEventMap, Vector3 } from "three";
2
2
 
3
3
  import { Gizmos } from "../engine/engine_gizmos.js";
4
4
  import { Mathf } from "../engine/engine_math.js";
5
5
  import { serializable } from "../engine/engine_serialization_decorator.js";
6
6
  import { getTempVector } from "../engine/engine_three_utils.js";
7
7
  import { getParam } from "../engine/engine_utils.js";
8
+ import { USDObject, USDZExporterContext } from "./api.js";
8
9
  import { Behaviour } from "./Component.js";
10
+ import { IUSDExporterExtension } from "./export/usdz/Extension.js";
11
+ import { USDZExporter } from "./export/usdz/USDZExporter.js";
9
12
  import { Renderer } from "./Renderer.js";
10
13
 
11
14
  const debugSeeThrough = getParam("debugseethrough");
@@ -102,6 +105,7 @@ export class SeeThrough extends Behaviour {
102
105
  onEnable() {
103
106
  this._needsUpdate = true;
104
107
  this._renderer = null;
108
+ SeeThroughUsdzExporterPlugin.components.push(this);
105
109
  }
106
110
 
107
111
  /** @internal */
@@ -118,6 +122,9 @@ export class SeeThrough extends Behaviour {
118
122
  this.rendererMaterials.delete(r);
119
123
  this.rendererMaterialsOriginal.delete(r);
120
124
  });
125
+
126
+ const index = SeeThroughUsdzExporterPlugin.components.indexOf(this);
127
+ if (index !== -1) SeeThroughUsdzExporterPlugin.components.splice(index, 1);
121
128
  }
122
129
 
123
130
  /**
@@ -253,4 +260,37 @@ export class SeeThrough extends Behaviour {
253
260
  });
254
261
  }
255
262
 
256
- }
263
+ }
264
+
265
+
266
+ ;
267
+ class SeeThroughUsdzExporterPlugin implements IUSDExporterExtension {
268
+
269
+ static readonly components: SeeThrough[] = [];
270
+
271
+ get extensionName() {
272
+ return "SeeThrough";
273
+ }
274
+
275
+ // onExportObject(object: Object3D<Object3DEventMap>, model: USDObject, context: USDZExporterContext) {
276
+ // const component = SeeThroughUsdzExporterPlugin.components.find(c => c.gameObject === object);
277
+ // if(!component) return;
278
+ // console.log("OH MY GOD SEE THROUGH USDZ EXPORTER", component, model);
279
+
280
+ // model.materialName = "AlphaHashMaterialInstance"; // we could make this unique per object if needed
281
+
282
+ // model.addEventListener("serialize", (writer, context) => {
283
+ // writer.appendLine(`# SeeThrough component on ${object.name}`);
284
+ // });
285
+ // }
286
+
287
+ }
288
+
289
+ const seeThroughUsdzExporterPlugin = new SeeThroughUsdzExporterPlugin();
290
+
291
+ USDZExporter.beforeExport.addEventListener(args => {
292
+ if (SeeThroughUsdzExporterPlugin.components.length === 0) return;
293
+ if (args.exporter.extensions.includes(seeThroughUsdzExporterPlugin) === false) {
294
+ args.exporter.extensions.push(seeThroughUsdzExporterPlugin);
295
+ }
296
+ });
@@ -112,6 +112,10 @@ const PositionIdentity = new Vector3();
112
112
  const QuaternionIdentity = new Quaternion();
113
113
  const ScaleIdentity = new Vector3(1,1,1);
114
114
 
115
+ // #region USDObject
116
+
117
+ type USDObjectEventType = "serialize" & ({} & string);
118
+
115
119
  class USDObject {
116
120
 
117
121
  static USDObject_export_id = 0;
@@ -153,12 +157,13 @@ class USDObject {
153
157
  private set isDynamic( value ) { this._isDynamic = value; }
154
158
  geometry: BufferGeometry | null;
155
159
  material: MeshStandardMaterial | MeshBasicMaterial | Material | MeshPhysicalNodeMaterial | null;
160
+ // usdMaterial?: USDMaterial;
156
161
  camera: PerspectiveCamera | OrthographicCamera | null;
157
162
  parent: USDObject | null;
158
163
  skinnedMesh: SkinnedMesh | null;
159
164
  children: Array<USDObject | null> = [];
160
165
  animations: AnimationClip[] | null;
161
- _eventListeners: {};
166
+ _eventListeners: Record<USDObjectEventType, Function[]>;
162
167
 
163
168
  // these are for tracking which xformops are needed
164
169
  needsTranslate: boolean = false;
@@ -201,7 +206,7 @@ class USDObject {
201
206
  this.camera = camera;
202
207
  this.parent = null;
203
208
  this.children = [];
204
- this._eventListeners = {};
209
+ this._eventListeners = {} as Record<USDObjectEventType, Function[]>;
205
210
  this._isDynamic = false;
206
211
  this.skinnedMesh = skinnedMesh;
207
212
  this.animations = animations;
@@ -287,7 +292,7 @@ class USDObject {
287
292
 
288
293
  }
289
294
 
290
- addEventListener( evt, listener: ( writer: USDWriter, context: USDZExporterContext ) => void ) {
295
+ addEventListener( evt : USDObjectEventType, listener: ( writer: USDWriter, context: USDZExporterContext ) => void ) {
291
296
 
292
297
  if ( ! this._eventListeners[ evt ] ) this._eventListeners[ evt ] = [];
293
298
  this._eventListeners[ evt ].push( listener );
@@ -316,6 +321,67 @@ class USDObject {
316
321
  }
317
322
 
318
323
 
324
+ // #region USDMaterial
325
+
326
+ // class MaterialInput {
327
+ // name: string;
328
+ // }
329
+
330
+ class USDMaterial {
331
+
332
+ static USDMaterial_id = 0;
333
+
334
+ readonly material: Material;
335
+ readonly id: number;
336
+
337
+ name: string;
338
+
339
+ isOverride: boolean = false;
340
+ isInstanceable: boolean = false;
341
+
342
+ constructor( material: Material ) {
343
+ this.material = material;
344
+ this.id = USDMaterial.USDMaterial_id ++;
345
+ this.name = makeNameSafe( material.name || 'Material_' + this.id );
346
+ }
347
+
348
+ readonly inputs: Record<string, string | number | boolean | number[] | undefined> = {};
349
+
350
+ // addInput( name: string, value: "" ) {
351
+
352
+
353
+
354
+ // }
355
+
356
+
357
+ serialize( writer: USDWriter, _context: USDZExporterContext ) {
358
+
359
+ const name = this.name;
360
+
361
+ writer.appendLine( `def Material "${this.name}" ${name ?`( displayName = "${makeDisplayNameSafe(name)}" )` : ''}` );
362
+ writer.beginBlock();
363
+
364
+
365
+ writer.closeBlock();
366
+
367
+ // def Material "${materialName}" ${material.name ?`(
368
+ // displayName = "${material.name}"
369
+ // )` : ''}
370
+ // {
371
+ // token outputs:mtlx:surface.connect = ${materialRoot}/${materialName}/Occlusion.outputs:out>
372
+
373
+ // def Shader "Occlusion"
374
+ // {
375
+ // uniform token info:id = "${mode}"
376
+ // token outputs:out
377
+ // }
378
+ // }`;
379
+
380
+ }
381
+ }
382
+
383
+ // #region USDDocument
384
+
319
385
  class USDDocument extends USDObject {
320
386
 
321
387
  stageLength: number;
@@ -448,6 +514,7 @@ ${comments}
448
514
 
449
515
  }
450
516
 
517
+
451
518
  const newLine = '\n';
452
519
  const materialRoot = '</StageRoot/Materials';
453
520
 
@@ -540,6 +607,8 @@ class USDWriter {
540
607
 
541
608
  declare type TextureMap = {[name: string]: {texture: Texture, scale?: Vector4}};
542
609
 
610
+ // #region USDZExporterContext
611
+
543
612
  class USDZExporterContext {
544
613
  root?: Object3D;
545
614
  exporter: USDZExporter;
@@ -576,6 +645,10 @@ class USDZExporterContext {
576
645
 
577
646
  }
578
647
 
648
+ makeNameSafe( str ) {
649
+ return makeNameSafe( str );
650
+ }
651
+
579
652
  }
580
653
 
581
654
  /**[documentation](https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/preliminary_anchoringapi/preliminary_anchoring_type) */
@@ -597,6 +670,8 @@ class USDZExporterOptions {
597
670
  exportInvisible: boolean = false;
598
671
  }
599
672
 
673
+ // #region USDZExporter
674
+
600
675
  class USDZExporter {
601
676
  debug: boolean;
602
677
  pruneUnusedNodes: boolean;
@@ -698,13 +773,7 @@ class USDZExporter {
698
773
  }
699
774
 
700
775
  Progress.report('export-usdz', { message: "Parsing document", autoStep: 10 });
701
- await parseDocument( context, () => {
702
- // injected after stageRoot.
703
- // TODO property use context/writer instead of string concat
704
- Progress.report('export-usdz', "Building materials");
705
- const result = buildMaterials( materials, textures, options.quickLookCompatible );
706
- return result;
707
- } );
776
+ await parseDocument( context, options );
708
777
 
709
778
  Progress.report("export-usdz", "Invoking onAfterSerialize");
710
779
  await invokeAll( context, 'onAfterSerialize' );
@@ -726,7 +795,7 @@ class USDZExporter {
726
795
  const final = header + '\n' + context.output;
727
796
 
728
797
  // full output file
729
- if ( this.debug ) console.log( final );
798
+ if ( this.debug ) console.debug( final );
730
799
 
731
800
  files[ modelFileName ] = fflate.strToU8( final );
732
801
  context.output = '';
@@ -822,6 +891,9 @@ class USDZExporter {
822
891
 
823
892
  }
824
893
 
894
+ // #endregion
895
+
896
+ // #region traverse
825
897
  function traverse( object: Object3D, parentModel: USDObject, context: USDZExporterContext, keepObject?: (object: Object3D) => boolean ) {
826
898
 
827
899
  if (!context.exportInvisible && !object.visible) return;
@@ -919,6 +991,8 @@ function traverse( object: Object3D, parentModel: USDObject, context: USDZExport
919
991
 
920
992
  }
921
993
 
994
+ // #endregion
995
+
922
996
  function logUsdHierarchy( object: USDObject, prefix: string, ...extraLogObjects: any[] ) {
923
997
 
924
998
  const item = {};
@@ -1020,7 +1094,8 @@ function prune ( object: USDObject, options : {
1020
1094
  return canBePruned;
1021
1095
  }
1022
1096
 
1023
- async function parseDocument( context: USDZExporterContext, afterStageRoot: () => string ) {
1097
+ // #region parseDocument
1098
+ async function parseDocument( context: USDZExporterContext, options: USDZExporterOptions ) {
1024
1099
 
1025
1100
  Progress.start("export-usdz-resources", "export-usdz");
1026
1101
  const resources: Array<() => void> = [];
@@ -1086,14 +1161,21 @@ async function parseDocument( context: USDZExporterContext, afterStageRoot: () =
1086
1161
 
1087
1162
  writer.closeBlock();
1088
1163
  writer.closeBlock();
1089
- writer.appendLine(afterStageRoot());
1164
+
1165
+ // TODO property use context/writer instead of string concat
1166
+ Progress.report('export-usdz', "Building materials");
1167
+ const result = buildMaterials( context.materials, context.textures, options.quickLookCompatible );
1168
+ writer.appendLine(result);
1169
+
1090
1170
  writer.closeBlock();
1091
1171
 
1092
1172
  Progress.report("export-usdz", "write to string")
1093
1173
  context.output += writer.toString();
1094
1174
 
1095
1175
  }
1176
+ // #endregion
1096
1177
 
1178
+ // #region addResources
1097
1179
  function addResources( object: USDObject | null, context: USDZExporterContext, resources: Array<() => void>) {
1098
1180
 
1099
1181
  if ( !object ) return;
@@ -1132,7 +1214,7 @@ function addResources( object: USDObject | null, context: USDZExporterContext, r
1132
1214
 
1133
1215
  if ( material ) {
1134
1216
 
1135
- if ( ! ( material.uuid in context.materials ) ) {
1217
+ if ( context.materials.get( material.uuid ) === undefined ) {
1136
1218
 
1137
1219
  context.materials[ material.uuid ] = material;
1138
1220
 
@@ -1169,6 +1251,10 @@ async function invokeAll( context: USDZExporterContext, name: string, writer: US
1169
1251
  }
1170
1252
 
1171
1253
  }
1254
+
1255
+ // #endregion
1256
+
1257
+ // #region GPU utils
1172
1258
  let _renderer: WebGLRenderer | null = null;
1173
1259
  let renderTarget: WebGLRenderTarget | null = null;
1174
1260
  let fullscreenQuadGeometry: PlaneGeometry | null;
@@ -1431,7 +1517,9 @@ function getPathToSkeleton(bone: Object3D, assumedRoot: Object3D) {
1431
1517
  return path;
1432
1518
  }
1433
1519
 
1434
- // Xform
1520
+ // #endregion
1521
+
1522
+ // #region XForm
1435
1523
 
1436
1524
  export function buildXform( model: USDObject | null, writer: USDWriter, context: USDZExporterContext ) {
1437
1525
 
@@ -1635,7 +1723,7 @@ function buildMatrixRow( array, offset ) {
1635
1723
 
1636
1724
  }
1637
1725
 
1638
- // Mesh
1726
+ // #region Mesh
1639
1727
 
1640
1728
  function buildMeshObject( geometry: BufferGeometry, bonesArray: Bone[] = [], quickLookCompatible: boolean = true) {
1641
1729
 
@@ -1948,7 +2036,9 @@ function buildVector2Array( attribute: BufferAttribute | InterleavedBufferAttrib
1948
2036
 
1949
2037
  }
1950
2038
 
1951
- // Materials
2039
+ // #endregion
2040
+
2041
+ // #region Materials
1952
2042
 
1953
2043
  function buildMaterials( materials: Map<string, Material>, textures: TextureMap, quickLookCompatible = false ) {
1954
2044
 
@@ -2103,10 +2193,14 @@ function buildTexture( texture: Texture, mapType: MapType, quickLookCompatible:
2103
2193
  }`;
2104
2194
  }
2105
2195
 
2196
+
2106
2197
  function buildMaterial( material: MeshBasicMaterial, textures: TextureMap, quickLookCompatible = false ) {
2107
2198
 
2108
2199
  const materialName = getMaterialName(material);
2109
2200
 
2201
+ // TODO: refactor to USDMaterial class
2202
+ // const usdMaterial = new USDMaterial(material);
2203
+
2110
2204
  // Special case: occluder material
2111
2205
  // Supported on iOS 18+ and visionOS 1+
2112
2206
  const isShadowCatcherMaterial =
@@ -2330,6 +2424,7 @@ function buildMaterial( material: MeshBasicMaterial, textures: TextureMap, quick
2330
2424
  }
2331
2425
  }
2332
2426
 
2427
+
2333
2428
  return `
2334
2429
 
2335
2430
  def Material "${materialName}" ${material.name ?`(
@@ -2368,6 +2463,8 @@ ${samplers.join( '\n' )}` : ''}
2368
2463
  }`;
2369
2464
  }
2370
2465
 
2466
+ // #endregion
2467
+
2371
2468
  function buildColor( color ) {
2372
2469
 
2373
2470
  return `(${color.r}, ${color.g}, ${color.b})`;
@@ -2407,6 +2504,9 @@ const formatsWithAlphaChannel = [
2407
2504
  36492, // RGBA_BPTC_Format
2408
2505
  ];
2409
2506
 
2507
+
2508
+ // #region exports
2509
+
2410
2510
  export {
2411
2511
  buildMatrix,
2412
2512
  decompressGpuTexture,
@@ -284,7 +284,7 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
284
284
  return opts;
285
285
  }
286
286
 
287
- private feedText(text: string, richText: boolean) : void {
287
+ private feedText(text: string, richText: boolean): void {
288
288
  // if (!text || text.length <= 0) return;
289
289
  // if (!text ) return;
290
290
  if (debug) console.log("feedText", this.uiObject, text, richText);
@@ -511,6 +511,15 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
511
511
  }
512
512
 
513
513
  private getFamilyNameWithCorrectSuffix(familyName: string, style: FontStyle): string {
514
+
515
+
516
+ // the URL decorator resolves the URL to absolute URLs - we need to remove the domain part since we're only interested in the path
517
+ if (familyName.startsWith("https:") || familyName.startsWith("http:")) {
518
+ const url = new URL(familyName);
519
+ familyName = url.pathname;
520
+ }
521
+
522
+
514
523
  // we can only change the style for the family if the name has a suffix (e.g. Arial-Bold)
515
524
  const styleSeparator = familyName.lastIndexOf('-');
516
525
  if (styleSeparator < 0) return familyName;
@@ -532,7 +541,7 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
532
541
  fontBaseName = fontBaseName.substring(pathSeparatorIndex + 1);
533
542
  }
534
543
  const isUpperCase = fontBaseName[0] === fontBaseName[0].toUpperCase();
535
- const fontNameWithoutSuffix = familyName.substring(0, styleSeparator);
544
+ const fontNameWithoutSuffix = familyName.substring(0, styleSeparator > pathSeparatorIndex ? styleSeparator : familyName.length);
536
545
  if (debug) console.log("Select font: ", familyName, FontStyle[style], fontBaseName, isUpperCase, fontNameWithoutSuffix);
537
546
 
538
547
  switch (style) {
@@ -11,6 +11,9 @@ import { Behaviour } from "../Component.js";
11
11
  /**
12
12
  * LookAt behaviour makes the object look at a target object or the camera.
13
13
  * It can also invert the forward direction and keep the up direction.
14
+ *
15
+ * @category Interactivity, Everywhere Actions
16
+ * @group Components
14
17
  */
15
18
  export class LookAt extends Behaviour implements UsdzBehaviour {
16
19