@needle-tools/engine 3.5.2-alpha → 3.5.3-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 (99) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/needle-engine.js +17123 -16970
  3. package/dist/needle-engine.min.js +349 -349
  4. package/dist/needle-engine.umd.cjs +347 -347
  5. package/lib/engine/codegen/register_types.js +4 -0
  6. package/lib/engine/codegen/register_types.js.map +1 -1
  7. package/lib/engine/debug/debug_overlay.js +2 -1
  8. package/lib/engine/debug/debug_overlay.js.map +1 -1
  9. package/lib/engine/engine_components.js +2 -1
  10. package/lib/engine/engine_components.js.map +1 -1
  11. package/lib/engine/engine_element_loading.js +2 -2
  12. package/lib/engine/engine_element_loading.js.map +1 -1
  13. package/lib/engine/engine_gameobject.js +2 -0
  14. package/lib/engine/engine_gameobject.js.map +1 -1
  15. package/lib/engine/engine_input.js +4 -1
  16. package/lib/engine/engine_input.js.map +1 -1
  17. package/lib/engine/engine_physics_rapier.js +2 -1
  18. package/lib/engine/engine_physics_rapier.js.map +1 -1
  19. package/lib/engine/engine_serialization_core.js +16 -1
  20. package/lib/engine/engine_serialization_core.js.map +1 -1
  21. package/lib/engine/extensions/NEEDLE_lighting_settings.js +10 -1
  22. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  23. package/lib/engine-components/Component.js +0 -3
  24. package/lib/engine-components/Component.js.map +1 -1
  25. package/lib/engine-components/codegen/components.d.ts +2 -0
  26. package/lib/engine-components/codegen/components.js +2 -0
  27. package/lib/engine-components/codegen/components.js.map +1 -1
  28. package/lib/engine-components/export/usdz/Extension.d.ts +3 -2
  29. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +41 -13
  30. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
  31. package/lib/engine-components/export/usdz/USDZExporter.d.ts +1 -0
  32. package/lib/engine-components/export/usdz/USDZExporter.js +30 -4
  33. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  34. package/lib/engine-components/export/usdz/extensions/behavior/AudioExtension.d.ts +9 -0
  35. package/lib/engine-components/export/usdz/extensions/behavior/AudioExtension.js +48 -0
  36. package/lib/engine-components/export/usdz/extensions/behavior/AudioExtension.js.map +1 -0
  37. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.d.ts +3 -0
  38. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js +18 -0
  39. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js.map +1 -1
  40. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +9 -0
  41. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +71 -1
  42. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  43. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.d.ts +20 -0
  44. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js +45 -0
  45. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js.map +1 -1
  46. package/lib/engine-components/ui/BaseUIComponent.d.ts +1 -0
  47. package/lib/engine-components/ui/BaseUIComponent.js +8 -4
  48. package/lib/engine-components/ui/BaseUIComponent.js.map +1 -1
  49. package/lib/engine-components/ui/Canvas.js +1 -1
  50. package/lib/engine-components/ui/Canvas.js.map +1 -1
  51. package/lib/engine-components/ui/EventSystem.js +3 -0
  52. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  53. package/lib/engine-components/ui/Image.d.ts +3 -1
  54. package/lib/engine-components/ui/Image.js +15 -1
  55. package/lib/engine-components/ui/Image.js.map +1 -1
  56. package/lib/engine-components/ui/Interfaces.d.ts +1 -1
  57. package/lib/engine-components/ui/Layout.js +2 -0
  58. package/lib/engine-components/ui/Layout.js.map +1 -1
  59. package/lib/engine-components/ui/PointerEvents.d.ts +8 -1
  60. package/lib/engine-components/ui/PointerEvents.js +9 -1
  61. package/lib/engine-components/ui/PointerEvents.js.map +1 -1
  62. package/lib/engine-components/ui/RaycastUtils.js +5 -0
  63. package/lib/engine-components/ui/RaycastUtils.js.map +1 -1
  64. package/lib/engine-components/ui/RectTransform.d.ts +2 -2
  65. package/lib/engine-components/ui/RectTransform.js +11 -12
  66. package/lib/engine-components/ui/RectTransform.js.map +1 -1
  67. package/lib/engine-components/ui/Text.d.ts +0 -2
  68. package/lib/engine-components/ui/Text.js +0 -5
  69. package/lib/engine-components/ui/Text.js.map +1 -1
  70. package/lib/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +1 -1
  72. package/src/engine/codegen/register_types.js +6 -2
  73. package/src/engine/debug/debug_overlay.ts +2 -1
  74. package/src/engine/engine_components.ts +2 -1
  75. package/src/engine/engine_element_loading.ts +2 -2
  76. package/src/engine/engine_gameobject.ts +3 -0
  77. package/src/engine/engine_input.ts +4 -1
  78. package/src/engine/engine_physics_rapier.ts +2 -1
  79. package/src/engine/engine_serialization_core.ts +17 -1
  80. package/src/engine/extensions/NEEDLE_lighting_settings.ts +11 -1
  81. package/src/engine-components/Component.ts +1 -3
  82. package/src/engine-components/codegen/components.ts +2 -0
  83. package/src/engine-components/export/usdz/Extension.ts +3 -2
  84. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +53 -14
  85. package/src/engine-components/export/usdz/USDZExporter.ts +34 -4
  86. package/src/engine-components/export/usdz/extensions/behavior/AudioExtension.ts +63 -0
  87. package/src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +23 -1
  88. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +79 -2
  89. package/src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts +46 -0
  90. package/src/engine-components/ui/BaseUIComponent.ts +8 -3
  91. package/src/engine-components/ui/Canvas.ts +1 -1
  92. package/src/engine-components/ui/EventSystem.ts +5 -1
  93. package/src/engine-components/ui/Image.ts +16 -1
  94. package/src/engine-components/ui/Interfaces.ts +1 -1
  95. package/src/engine-components/ui/Layout.ts +2 -0
  96. package/src/engine-components/ui/PointerEvents.ts +16 -2
  97. package/src/engine-components/ui/RaycastUtils.ts +6 -1
  98. package/src/engine-components/ui/RectTransform.ts +11 -11
  99. package/src/engine-components/ui/Text.ts +1 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "3.5.2-alpha",
3
+ "version": "3.5.3-alpha",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in",
5
5
  "main": "dist/needle-engine.umd.cjs",
6
6
  "type": "module",
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components";
5
5
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
@@ -14,6 +14,7 @@ import { Animator } from "../../engine-components/Animator";
14
14
  import { AnimatorController } from "../../engine-components/AnimatorController";
15
15
  import { Antialiasing } from "../../engine-components/postprocessing/Effects/Antialiasing";
16
16
  import { AttachedObject } from "../../engine-components/webxr/WebXRController";
17
+ import { AudioExtension } from "../../engine-components/export/usdz/extensions/behavior/AudioExtension";
17
18
  import { AudioListener } from "../../engine-components/AudioListener";
18
19
  import { AudioSource } from "../../engine-components/AudioSource";
19
20
  import { AudioTrackHandler } from "../../engine-components/timeline/TimelineTracks";
@@ -116,6 +117,7 @@ import { ParticleSystemRenderer } from "../../engine-components/ParticleSystem";
116
117
  import { PixelationEffect } from "../../engine-components/postprocessing/Effects/Pixelation";
117
118
  import { PlayableDirector } from "../../engine-components/timeline/PlayableDirector";
118
119
  import { PlayAnimationOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
120
+ import { PlayAudioOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
119
121
  import { PlayerColor } from "../../engine-components/PlayerColor";
120
122
  import { PlayerState } from "../../engine-components-experimental/networking/PlayerSync";
121
123
  import { PlayerSync } from "../../engine-components-experimental/networking/PlayerSync";
@@ -213,7 +215,7 @@ import { XRGrabModel } from "../../engine-components/webxr/WebXRGrabRendering";
213
215
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
214
216
  import { XRRig } from "../../engine-components/webxr/WebXRRig";
215
217
  import { XRState } from "../../engine-components/XRFlag";
216
-
218
+
217
219
  // Register types
218
220
  TypeStore.add("__Ignore", __Ignore);
219
221
  TypeStore.add("ActionBuilder", ActionBuilder);
@@ -228,6 +230,7 @@ TypeStore.add("Animator", Animator);
228
230
  TypeStore.add("AnimatorController", AnimatorController);
229
231
  TypeStore.add("Antialiasing", Antialiasing);
230
232
  TypeStore.add("AttachedObject", AttachedObject);
233
+ TypeStore.add("AudioExtension", AudioExtension);
231
234
  TypeStore.add("AudioListener", AudioListener);
232
235
  TypeStore.add("AudioSource", AudioSource);
233
236
  TypeStore.add("AudioTrackHandler", AudioTrackHandler);
@@ -330,6 +333,7 @@ TypeStore.add("ParticleSystemRenderer", ParticleSystemRenderer);
330
333
  TypeStore.add("PixelationEffect", PixelationEffect);
331
334
  TypeStore.add("PlayableDirector", PlayableDirector);
332
335
  TypeStore.add("PlayAnimationOnClick", PlayAnimationOnClick);
336
+ TypeStore.add("PlayAudioOnClick", PlayAudioOnClick);
333
337
  TypeStore.add("PlayerColor", PlayerColor);
334
338
  TypeStore.add("PlayerState", PlayerState);
335
339
  TypeStore.add("PlayerSync", PlayerSync);
@@ -107,7 +107,8 @@ const currentMessages = new Set<string>();
107
107
  function showMessage(type: LogType, element: HTMLElement, msg: string) {
108
108
  const container = getLogsContainer(element);
109
109
  if (container.childElementCount >= 20) {
110
- return;
110
+ const last = container.lastElementChild;
111
+ returnMessageContainer(last as HTMLElement);
111
112
  }
112
113
  // truncate long messages before they go into the cache/set
113
114
  if(msg.length > 300) msg = msg.substring(0, 300) + "...";
@@ -160,7 +160,8 @@ export function getComponent<T>(obj: Object3D, componentType: Constructor<T>) {
160
160
 
161
161
  export function getComponents<T>(obj: Object3D, componentType: Constructor<T>, arr?: T[] | null): T[] {
162
162
  if (!arr) arr = [];
163
- return onGetComponent(obj, componentType, arr);
163
+ onGetComponent(obj, componentType, arr);
164
+ return arr;
164
165
  }
165
166
 
166
167
  export function getComponentInChildren<T>(obj: Object3D, componentType: Constructor<T>, includeInactive?: boolean) {
@@ -186,7 +186,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
186
186
  if (loadingStyle === "light")
187
187
  this._loadingElement.style.backgroundColor = "#ddd";
188
188
  else
189
- this._loadingElement.style.backgroundColor = "#000";
189
+ this._loadingElement.style.backgroundColor = "#222";
190
190
  this._loadingElement.style.display = "flex";
191
191
  this._loadingElement.style.alignItems = "center";
192
192
  this._loadingElement.style.justifyContent = "center";
@@ -289,7 +289,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
289
289
  messageContainer.style.fontSize = ".8em";
290
290
  messageContainer.style.paddingTop = ".5em";
291
291
  messageContainer.style.fontWeight = "200";
292
- messageContainer.style.fontFamily = "Roboto, sans-serif";
292
+ messageContainer.style.fontFamily = "Roboto, sans-serif, Arial";
293
293
  // messageContainer.style.border = "1px solid rgba(255,255,255,.1)";
294
294
  messageContainer.style.justifyContent = "center";
295
295
  this._loadingElement.appendChild(messageContainer);
@@ -113,6 +113,9 @@ export function destroy(instance: Object3D | Component, recursive: boolean = tru
113
113
  }
114
114
 
115
115
  function internalDestroy(instance: Object3D | Component, recursive: boolean = true, dispose: boolean = false, isRoot: boolean = true) {
116
+ if (instance === null || instance === undefined)
117
+ return;
118
+
116
119
  const comp = instance as Component;
117
120
  if (comp.isComponent) {
118
121
  comp.__internalDisable();
@@ -611,9 +611,12 @@ export class Input extends EventTarget implements IInput {
611
611
 
612
612
  const lf = this._pointerPositionsLastFrame[evt.button];
613
613
  lf.copy(this._pointerPositions[evt.button]);
614
+ // accumulate delta (it's reset in end of frame), if we just write it here it's not correct when the browser console is open
615
+ const delta = this._pointerPositionsDelta[evt.button];
614
616
  const dx = evt.clientX - lf.x;
615
617
  const dy = evt.clientY - lf.y;
616
- this._pointerPositionsDelta[evt.button].set(dx, dy);
618
+ delta.x += dx;
619
+ delta.y += dy;
617
620
 
618
621
  this._pointerPositions[evt.button].x = evt.clientX;
619
622
  this._pointerPositions[evt.button].y = evt.clientY;
@@ -207,7 +207,8 @@ export class RapierPhysics implements IPhysicsEngine {
207
207
  private async internalInitialization() {
208
208
  // NEEDLE_PHYSICS_INIT_START
209
209
  // use .env file with VITE_NEEDLE_USE_RAPIER=false to treeshape rapier
210
- if (import.meta.env.VITE_NEEDLE_USE_RAPIER === "false") {
210
+ // @ts-ignore
211
+ if ("env" in import.meta && import.meta.env.VITE_NEEDLE_USE_RAPIER === "false") {
211
212
  return false;
212
213
  }
213
214
  // Can be transformed during build time to disable rapier
@@ -434,7 +434,14 @@ function implictlyAssignPrimitiveTypes(obj: any, serializedData: any) {
434
434
  if (targetMember !== undefined) continue;
435
435
  // resolve serialized primitive types
436
436
  if (isPrimitiveType(data[key]) && !isPrimitiveType(member)) {
437
- // console.log("ASSIGN", key, member, member[key], targetMember, data[key]);
437
+
438
+ const prop = tryFindPropertyDescriptor(member, key);
439
+ if (!prop?.writable === false || (prop && prop.set === undefined)) {
440
+ if (debug)
441
+ console.warn("Property is not writable \"" + key + "\"", member, prop, data[key], member[key]);
442
+ continue;
443
+ }
444
+ // console.log("ASSIGN", key, member, member[key], targetMember, data[key], prop);
438
445
  member[key] = data[key];
439
446
  }
440
447
  }
@@ -442,6 +449,15 @@ function implictlyAssignPrimitiveTypes(obj: any, serializedData: any) {
442
449
  }
443
450
  }
444
451
 
452
+ function tryFindPropertyDescriptor(obj: object, key: string) : PropertyDescriptor | undefined {
453
+ while(obj){
454
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
455
+ if(desc) return desc;
456
+ obj = Object.getPrototypeOf(obj);
457
+ }
458
+ return undefined;
459
+ }
460
+
445
461
  function isPrimitiveType(val): boolean {
446
462
  switch (typeof val) {
447
463
  case "number":
@@ -48,8 +48,9 @@ export class NEEDLE_lighting_settings implements GLTFLoaderPlugin {
48
48
  let settings: SceneLightSettings | undefined = undefined;
49
49
  // If the result scene has only one child we add the LightingSettingsComponent to that child
50
50
  if (_result.scene.children.length === 1) {
51
+ const obj = _result.scene.children[0];
51
52
  // add a component to the root of the scene
52
- settings = GameObject.addNewComponent(_result.scene.children[0], SceneLightSettings, false);
53
+ settings = GameObject.addNewComponent(obj, SceneLightSettings, false);
53
54
  }
54
55
  // if the scene already has multiple children we add it as a new object
55
56
  else {
@@ -117,6 +118,15 @@ export class SceneLightSettings extends Behaviour {
117
118
  }
118
119
  });
119
120
  }
121
+
122
+ // make sure the component is in the end of the list
123
+ // (e.g. if we have an animation on the first component from an instance and add the scenelightingcomponent the animation binding will break)
124
+ const comps = this.gameObject.userData?.components;
125
+ if (comps) {
126
+ const index = comps.indexOf(this);
127
+ comps.splice(index, 1);
128
+ comps.push(this);
129
+ }
120
130
  }
121
131
 
122
132
  onDestroy(): void {
@@ -195,9 +195,6 @@ export abstract class GameObject extends Object3D implements Object3D, IGameObje
195
195
  * @param instance component to move to the GO
196
196
  */
197
197
  public static moveComponent(go: IGameObject, instance: Component): void {
198
- if (instance.gameObject == null) {
199
- throw new Error("Did you mean to create a new component? Use addNewComponent");
200
- }
201
198
  moveComponentInstance(go, instance as any);
202
199
  }
203
200
 
@@ -277,6 +274,7 @@ export abstract class GameObject extends Object3D implements Object3D, IGameObje
277
274
  // these are implemented via threejs object extensions
278
275
  abstract activeSelf: boolean;
279
276
  abstract addNewComponent<T>(type: Constructor<T>): T | null;
277
+ // TODO: add method for addExisting component
280
278
  abstract removeComponent(comp: Component): Component;
281
279
  abstract getOrAddComponent<T>(typeName: Constructor<T> | null): T;
282
280
  abstract getComponent<T>(type: Constructor<T>): T | null;
@@ -12,6 +12,7 @@ export { Animator } from "../Animator";
12
12
  export { AnimatorController } from "../AnimatorController";
13
13
  export { Antialiasing } from "../postprocessing/Effects/Antialiasing";
14
14
  export { AttachedObject } from "../webxr/WebXRController";
15
+ export { AudioExtension } from "../export/usdz/extensions/behavior/AudioExtension";
15
16
  export { AudioListener } from "../AudioListener";
16
17
  export { AudioSource } from "../AudioSource";
17
18
  export { AudioTrackHandler } from "../timeline/TimelineTracks";
@@ -114,6 +115,7 @@ export { ParticleSystemRenderer } from "../ParticleSystem";
114
115
  export { PixelationEffect } from "../postprocessing/Effects/Pixelation";
115
116
  export { PlayableDirector } from "../timeline/PlayableDirector";
116
117
  export { PlayAnimationOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents";
118
+ export { PlayAudioOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents";
117
119
  export { PlayerColor } from "../PlayerColor";
118
120
  export { PointerEventData } from "../ui/PointerEvents";
119
121
  export { PostProcessingHandler } from "../postprocessing/PostProcessingHandler";
@@ -1,11 +1,12 @@
1
- import { USDObject } from "./ThreeUSDZExporter";
1
+ import { USDObject, USDZExporterContext } from "./ThreeUSDZExporter";
2
+ import { Object3D } from "three";
2
3
 
3
4
  export interface IUSDExporterExtension {
4
5
 
5
6
  get extensionName(): string;
6
7
  onBeforeBuildDocument?(context);
7
8
  onAfterBuildDocument?(context);
8
- onExportObject?(object, model : USDObject, context);
9
+ onExportObject?(object: Object3D, model : USDObject, context: USDZExporterContext);
9
10
  onAfterSerialize?(context);
10
11
  onAfterHierarchy?(context, writer : any);
11
12
  }
@@ -1,3 +1,5 @@
1
+ import { Renderer } from '../../Renderer';
2
+ import { GameObject } from '../../Component';
1
3
  import {
2
4
  PlaneGeometry,
3
5
  Texture,
@@ -19,6 +21,9 @@ import {
19
21
  MeshStandardMaterial,
20
22
  sRGBEncoding,
21
23
  MeshPhysicalMaterial,
24
+ Object3D,
25
+ MeshBasicMaterial,
26
+ SkinnedMesh,
22
27
  } from 'three';
23
28
  import * as fflate from 'three/examples/jsm/libs/fflate.module.js';
24
29
 
@@ -436,7 +441,7 @@ class USDZExporter {
436
441
 
437
442
  parseDocument( context );
438
443
 
439
- invokeAll( context, 'onAfterSerialize' );
444
+ await invokeAll( context, 'onAfterSerialize' );
440
445
 
441
446
  context.output += buildMaterials( materials, textures );
442
447
 
@@ -509,21 +514,33 @@ class USDZExporter {
509
514
 
510
515
  }
511
516
 
512
- function traverseVisible( object, parentModel, context ) {
517
+ function traverseVisible( object: Object3D, parentModel: USDObject, context: USDZExporterContext ) {
513
518
 
514
519
  if ( ! object.visible ) return;
515
-
520
+
516
521
  let model: USDObject | undefined = undefined;
517
- const geometry = object.geometry;
518
- const material = object.material;
522
+ let geometry: BufferGeometry | undefined = undefined;
523
+ let material: Material | Material[] | undefined = undefined;
524
+
525
+ if (object instanceof Mesh) {
526
+ geometry = object.geometry;
527
+ material = object.material;
528
+ }
519
529
 
530
+ // TODO what should be do with disabled renderers?
531
+ // Here we just assume they're off, and don't export them
532
+ const renderer = GameObject.getComponent( object, Renderer )
533
+ if (renderer && !renderer.enabled) {
534
+ geometry = undefined;
535
+ material = undefined;
536
+ }
520
537
 
521
- if ( object.isMesh && material && (material.isMeshStandardMaterial || material.isMeshBasicMaterial) && ! object.isSkinnedMesh ) {
538
+ if ( object instanceof Mesh && material && (material instanceof MeshStandardMaterial || material instanceof MeshBasicMaterial) && ! (object instanceof SkinnedMesh )) {
522
539
 
523
540
  const name = getObjectId( object );
524
541
  model = new USDObject( object.uuid, name, object.matrix, geometry, material );
525
542
 
526
- } else if ( object.isCamera ) {
543
+ } else if ( object instanceof Camera ) {
527
544
 
528
545
  const name = getObjectId( object );
529
546
  model = new USDObject( object.uuid, name, object.matrix, undefined, undefined, object );
@@ -577,7 +594,7 @@ function traverseVisible( object, parentModel, context ) {
577
594
 
578
595
  }
579
596
 
580
- function parseDocument( context: USDZExporterContext ) {
597
+ async function parseDocument( context: USDZExporterContext ) {
581
598
 
582
599
  for ( const child of context.document.children ) {
583
600
 
@@ -666,14 +683,27 @@ function addResources( object, context: USDZExporterContext ) {
666
683
 
667
684
  }
668
685
 
669
- function invokeAll( context: USDZExporterContext, name: string, writer: USDWriter | null = null ) {
686
+ async function invokeAll( context: USDZExporterContext, name: string, writer: USDWriter | null = null ) {
670
687
 
671
688
  if ( context.extensions ) {
672
689
 
673
690
  for ( const ext of context.extensions ) {
674
691
 
675
- if ( typeof ext[ name ] === 'function' )
676
- ext[ name ]( context, writer );
692
+ if ( !ext ) continue;
693
+
694
+ if ( typeof ext[ name ] === 'function' ) {
695
+
696
+ const method = ext[ name ];
697
+
698
+ const isAsync = method.constructor.name === "AsyncFunction";
699
+
700
+ if ( isAsync ) {
701
+ await method.call( ext, context, writer );
702
+ } else {
703
+ method.call( ext, context, writer );
704
+ }
705
+
706
+ }
677
707
 
678
708
  }
679
709
 
@@ -841,6 +871,14 @@ export function buildXform( model, writer, context ) {
841
871
  const material = model.material;
842
872
  const camera = model.camera;
843
873
  const name = model.name;
874
+
875
+ // postprocess node
876
+ if ( model.onBeforeSerialize ) {
877
+
878
+ model.onBeforeSerialize( writer, context );
879
+
880
+ }
881
+
844
882
  const transform = buildMatrix( matrix );
845
883
 
846
884
  if ( matrix.determinant() < 0 ) {
@@ -849,11 +887,12 @@ export function buildXform( model, writer, context ) {
849
887
 
850
888
  }
851
889
 
852
- if ( geometry )
890
+ if ( geometry ) {
853
891
  writer.beginBlock( `def Xform "${name}" (
854
- prepend references = @./geometries/Geometry_${geometry.id}.usd@</Geometry>
855
- prepend apiSchemas = ["MaterialBindingAPI"]
892
+ prepend references = @./geometries/Geometry_${geometry.id}.usd@</Geometry>
893
+ prepend apiSchemas = ["MaterialBindingAPI"]
856
894
  )` );
895
+ }
857
896
  else if ( camera )
858
897
  writer.beginBlock( `def Camera "${name}"` );
859
898
  else
@@ -14,6 +14,7 @@ import { Context } from "../../../engine/engine_setup";
14
14
  import { WebARSessionRoot } from "../../webxr/WebARSessionRoot";
15
15
  import { hasProLicense } from "../../../engine/engine_license";
16
16
  import { BehaviorExtension } from "./extensions/behavior/Behaviour";
17
+ import { AudioExtension } from "./extensions/behavior/AudioExtension";
17
18
 
18
19
  const debug = getParam("debugusdz");
19
20
 
@@ -41,6 +42,9 @@ export class USDZExporter extends Behaviour {
41
42
  @serializable()
42
43
  exportFileName?: string;
43
44
 
45
+ @serializable(URL)
46
+ customUsdzFile?: string;
47
+
44
48
  @serializable(QuickLookOverlay)
45
49
  overlay?: QuickLookOverlay;
46
50
 
@@ -91,6 +95,7 @@ export class USDZExporter extends Behaviour {
91
95
 
92
96
  if (this.interactive) {
93
97
  this.extensions.push(new BehaviorExtension());
98
+ this.extensions.push(new AudioExtension());
94
99
  }
95
100
  }
96
101
 
@@ -120,6 +125,35 @@ export class USDZExporter extends Behaviour {
120
125
  }
121
126
 
122
127
  async exportAsync() {
128
+
129
+ let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
130
+ if (!hasProLicense()) name += "-MadeWithNeedle";
131
+ name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
132
+
133
+ // ability to specify a custom USDZ file to be used instead of a dynamic one
134
+ if (this.customUsdzFile) {
135
+
136
+ // see https://developer.apple.com/documentation/arkit/adding_an_apple_pay_button_or_a_custom_action_in_ar_quick_look
137
+ const overlay = this.buildQuicklookOverlay();
138
+ if(debug) console.log(overlay);
139
+ const callToAction = overlay.callToAction ? encodeURIComponent(overlay.callToAction) : "";
140
+ const checkoutTitle = overlay.checkoutTitle ? encodeURIComponent(overlay.checkoutTitle) : "";
141
+ const checkoutSubtitle = overlay.checkoutSubtitle ? encodeURIComponent(overlay.checkoutSubtitle) : "";
142
+ this.link.href = this.customUsdzFile + `#callToAction=${callToAction}&checkoutTitle=${checkoutTitle}&checkoutSubtitle=${checkoutSubtitle}&callToActionURL=${overlay.callToActionURL}`;
143
+
144
+ console.log(this.link.href)
145
+
146
+ if (!this.lastCallback) {
147
+ this.lastCallback = this.quicklookCallback.bind(this)
148
+ this.link.addEventListener('message', this.lastCallback);
149
+ }
150
+
151
+ // open quicklook
152
+ this.link.download = name + ".usdz";
153
+ this.link.click();
154
+ return;
155
+ }
156
+
123
157
  if (!this.objectToExport) return;
124
158
 
125
159
  // make sure we apply the AR scale
@@ -145,10 +179,6 @@ export class USDZExporter extends Behaviour {
145
179
  const eventArgs = { self: this, exporter: exporter, extensions: extensions, object: this.objectToExport };
146
180
  this.dispatchEvent(new CustomEvent("before-export", { detail: eventArgs }))
147
181
 
148
- let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
149
- if (!hasProLicense()) name += "-MadeWithNeedle";
150
- name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
151
-
152
182
  //@ts-ignore
153
183
  exporter.debug = debug;
154
184
 
@@ -0,0 +1,63 @@
1
+ import { GameObject } from "../../../../Component";
2
+ import { IUSDExporterExtension } from "../../Extension";
3
+ import { USDObject, USDWriter, USDZExporterContext } from "../../ThreeUSDZExporter";
4
+ import { Object3D } from "three";
5
+ import { AudioSource } from "../../../../AudioSource";
6
+
7
+ export class AudioExtension implements IUSDExporterExtension {
8
+
9
+ get extensionName(): string {
10
+ return "Audio";
11
+ }
12
+
13
+ private files: string[] = [];
14
+
15
+ onExportObject?(object: Object3D, model : USDObject, _context: USDZExporterContext) {
16
+ // check if this object has an audio source, add the relevant schema in that case.
17
+ const audioSources = GameObject.getComponents(object, AudioSource);
18
+ if (audioSources.length) {
19
+ for (const audioSource of audioSources) {
20
+
21
+ // do nothing if this audio source is not set to play on awake -
22
+ // should be controlled via PlayAudioOnClick instead then.
23
+ if (!audioSource.playOnAwake)
24
+ continue;
25
+
26
+ const clipName = audioSource.clip.split("/").pop();
27
+
28
+ if (!this.files.includes(audioSource.clip)) {
29
+ this.files.push(audioSource.clip);
30
+ }
31
+
32
+ model.addEventListener('serialize', (writer: USDWriter, _context: USDZExporterContext) => {
33
+ writer.appendLine();
34
+ writer.beginBlock(`def SpatialAudio "${model.name}"`);
35
+ writer.appendLine(`uniform asset filePath = @audio/${clipName}@`);
36
+ writer.appendLine(`uniform token auralMode = "${ audioSource.spatialBlend > 0 ? "spatial" : "nonSpatial" }"`);
37
+ // theoretically we could do timeline-like audio sequencing with this.
38
+ writer.appendLine(`uniform token playbackMode = "${audioSource.loop ? "loopFromStage" : "onceFromStart" }"`);
39
+ writer.appendLine(`uniform float gain = ${audioSource.volume}`);
40
+ writer.closeBlock();
41
+ });
42
+ }
43
+ }
44
+ }
45
+
46
+ async onAfterSerialize(context: USDZExporterContext) {
47
+ console.warn("onAfterSerialize", this);
48
+ // write the files to the context.
49
+ for (const file of this.files) {
50
+
51
+ const clipName = file.split("/").pop();
52
+
53
+ // convert file (which is a path) to a blob.
54
+ const audio = await fetch(file);
55
+ const audioBlob = await audio.blob();
56
+ const arrayBuffer = await audioBlob.arrayBuffer();
57
+
58
+ const audioData: Uint8Array = new Uint8Array(arrayBuffer)
59
+
60
+ context.files["audio/" + clipName] = audioData;
61
+ }
62
+ }
63
+ }
@@ -8,6 +8,7 @@ export interface UsdzBehaviour {
8
8
  createBehaviours?(ext: BehaviorExtension, model: USDObject, context: IContext): void;
9
9
  beforeCreateDocument?(ext: BehaviorExtension, context: IContext): void;
10
10
  afterCreateDocument?(ext: BehaviorExtension, context: IContext): void;
11
+ afterSerialize?(ext: BehaviorExtension, context: IContext): void;
11
12
  }
12
13
 
13
14
  export class BehaviorExtension implements IUSDExporterExtension {
@@ -23,6 +24,7 @@ export class BehaviorExtension implements IUSDExporterExtension {
23
24
  }
24
25
 
25
26
  behaviourComponents: Array<UsdzBehaviour> = [];
27
+ private behaviourComponentsCopy: Array<UsdzBehaviour> = [];
26
28
 
27
29
 
28
30
  onBeforeBuildDocument(context) {
@@ -42,7 +44,6 @@ export class BehaviorExtension implements IUSDExporterExtension {
42
44
  }
43
45
 
44
46
  onExportObject(_object, model: USDObject, context) {
45
-
46
47
  for (const beh of this.behaviourComponents) {
47
48
  beh.createBehaviours?.call(beh, this, model, context);
48
49
  }
@@ -53,6 +54,7 @@ export class BehaviorExtension implements IUSDExporterExtension {
53
54
  if (typeof beh.afterCreateDocument === "function")
54
55
  beh.afterCreateDocument(this, context);
55
56
  }
57
+ this.behaviourComponentsCopy = this.behaviourComponents.slice();
56
58
  this.behaviourComponents.length = 0;
57
59
  }
58
60
 
@@ -70,6 +72,26 @@ export class BehaviorExtension implements IUSDExporterExtension {
70
72
  }
71
73
  }
72
74
 
75
+ async onAfterSerialize(context) {
76
+ console.log("onAfterSerialize", this.behaviourComponentsCopy)
77
+ for (const beh of this.behaviourComponentsCopy) {
78
+
79
+ console.log("behaviour", beh)
80
+ if (typeof beh.afterSerialize === "function") {
81
+
82
+ console.log("beh has afterSerialize", beh)
83
+
84
+ const isAsync = beh.afterSerialize.constructor.name === "AsyncFunction";
85
+
86
+ if ( isAsync ) {
87
+ await beh.afterSerialize(this, context);
88
+ } else {
89
+ beh.afterSerialize(this, context);
90
+ }
91
+ }
92
+ }
93
+ }
94
+
73
95
  // combine behaviours that have tap triggers on the same object
74
96
  // private combineBehavioursWithSameTapActions() {
75
97
  // // TODO: if behaviours have different settings (e.g. one is exclusive and another one is not) this wont work - we need more logic for that