@needle-tools/engine 3.5.1-alpha → 3.5.2-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 (42) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/needle-engine.js +10373 -10253
  3. package/dist/needle-engine.min.js +320 -319
  4. package/dist/needle-engine.umd.cjs +316 -315
  5. package/lib/engine/codegen/register_types.js +0 -2
  6. package/lib/engine/codegen/register_types.js.map +1 -1
  7. package/lib/engine/engine_element_loading.js +2 -2
  8. package/lib/engine/engine_element_loading.js.map +1 -1
  9. package/lib/engine/engine_license.d.ts +2 -0
  10. package/lib/engine/engine_license.js +25 -4
  11. package/lib/engine/engine_license.js.map +1 -1
  12. package/lib/engine-components/SceneSwitcher.d.ts +9 -0
  13. package/lib/engine-components/SceneSwitcher.js +128 -0
  14. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  15. package/lib/engine-components/codegen/components.d.ts +0 -1
  16. package/lib/engine-components/codegen/components.js +0 -1
  17. package/lib/engine-components/codegen/components.js.map +1 -1
  18. package/lib/engine-components/export/usdz/ThreeUSDZExporter.d.ts +9 -5
  19. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +23 -7
  20. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
  21. package/lib/engine-components/export/usdz/USDZExporter.d.ts +1 -0
  22. package/lib/engine-components/export/usdz/USDZExporter.js +14 -5
  23. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  24. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.d.ts +1 -5
  25. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js +1 -10
  26. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js.map +1 -1
  27. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +4 -4
  28. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  29. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js.map +1 -1
  30. package/lib/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +1 -1
  32. package/plugins/vite/license.js +2 -2
  33. package/src/engine/codegen/register_types.js +2 -4
  34. package/src/engine/engine_element_loading.ts +2 -2
  35. package/src/engine/engine_license.ts +25 -4
  36. package/src/engine-components/SceneSwitcher.ts +136 -1
  37. package/src/engine-components/codegen/components.ts +0 -1
  38. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +36 -7
  39. package/src/engine-components/export/usdz/USDZExporter.ts +14 -7
  40. package/src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +5 -15
  41. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +19 -19
  42. package/src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "3.5.1-alpha",
3
+ "version": "3.5.2-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",
@@ -13,8 +13,8 @@ export const needleLicense = (command, config, userSettings) => {
13
13
  if (isNeedleEngineFile || isViteChunkFile) {
14
14
  const needleConfig = await loadConfig();
15
15
  if (needleConfig) {
16
- if (needleConfig.hasProLicense === true) {
17
- src = src.replace("NEEDLE_ENGINE_COMMERCIAL_USE_LICENSE = false;", "NEEDLE_ENGINE_COMMERCIAL_USE_LICENSE = " + needleConfig.hasProLicense + ";");
16
+ if (typeof needleConfig.license === "string") {
17
+ src = src.replace("const NEEDLE_ENGINE_LICENSE_TYPE: string = \"\";", "const NEEDLE_ENGINE_LICENSE_TYPE: string = \"" + needleConfig.license + "\";");
18
18
  return { code: src, map: null }
19
19
  }
20
20
  }
@@ -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";
@@ -184,7 +184,6 @@ import { TriggerModel } from "../../engine-components/export/usdz/extensions/beh
184
184
  import { UIRaycastUtils } from "../../engine-components/ui/RaycastUtils";
185
185
  import { UIRootComponent } from "../../engine-components/ui/BaseUIComponent";
186
186
  import { UsageMarker } from "../../engine-components/Interactable";
187
- import { USDZBehaviours } from "../../engine-components/export/usdz/extensions/behavior/Behaviour";
188
187
  import { USDZExporter } from "../../engine-components/export/usdz/USDZExporter";
189
188
  import { USDZText } from "../../engine-components/export/usdz/extensions/USDZText";
190
189
  import { VariantAction } from "../../engine-components/export/usdz/extensions/behavior/Actions";
@@ -214,7 +213,7 @@ import { XRGrabModel } from "../../engine-components/webxr/WebXRGrabRendering";
214
213
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
215
214
  import { XRRig } from "../../engine-components/webxr/WebXRRig";
216
215
  import { XRState } from "../../engine-components/XRFlag";
217
-
216
+
218
217
  // Register types
219
218
  TypeStore.add("__Ignore", __Ignore);
220
219
  TypeStore.add("ActionBuilder", ActionBuilder);
@@ -399,7 +398,6 @@ TypeStore.add("TriggerModel", TriggerModel);
399
398
  TypeStore.add("UIRaycastUtils", UIRaycastUtils);
400
399
  TypeStore.add("UIRootComponent", UIRootComponent);
401
400
  TypeStore.add("UsageMarker", UsageMarker);
402
- TypeStore.add("USDZBehaviours", USDZBehaviours);
403
401
  TypeStore.add("USDZExporter", USDZExporter);
404
402
  TypeStore.add("USDZText", USDZText);
405
403
  TypeStore.add("VariantAction", VariantAction);
@@ -3,7 +3,7 @@ import { Mathf } from "./engine_math";
3
3
  import { LoadingProgressArgs } from "./engine_setup";
4
4
  import { getParam } from "./engine_utils";
5
5
  import { logoSVG } from "./assets"
6
- import { hasProLicense } from "./engine_license";
6
+ import { hasCommercialLicense, hasProLicense } from "./engine_license";
7
7
 
8
8
  const debug = getParam("debugloadingbar");
9
9
  const debugRendering = getParam("debugloadingbarrendering");
@@ -301,7 +301,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
301
301
  }
302
302
  }
303
303
 
304
- if (!hasLicense) {
304
+ if (!hasCommercialLicense()) {
305
305
  const nonCommercialContainer = document.createElement("div");
306
306
  nonCommercialContainer.style.paddingTop = ".6em";
307
307
  nonCommercialContainer.style.fontSize = ".8em";
@@ -7,10 +7,28 @@ const debug = getParam("debuglicense");
7
7
 
8
8
  // This is modified by a bundler (e.g. vite)
9
9
  // Do not edit manually
10
- const NEEDLE_ENGINE_COMMERCIAL_USE_LICENSE = false;
10
+ const NEEDLE_ENGINE_LICENSE_TYPE: string = "";
11
+ if (debug) console.log("License Type: " + NEEDLE_ENGINE_LICENSE_TYPE)
11
12
 
12
13
  export function hasProLicense() {
13
- return NEEDLE_ENGINE_COMMERCIAL_USE_LICENSE;
14
+ switch (NEEDLE_ENGINE_LICENSE_TYPE) {
15
+ case "pro":
16
+ case "enterprise":
17
+ return true;
18
+ };
19
+ return false;
20
+ }
21
+
22
+ export function hasIndieLicense() {
23
+ switch (NEEDLE_ENGINE_LICENSE_TYPE) {
24
+ case "indie":
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+
30
+ export function hasCommercialLicense() {
31
+ return hasProLicense() || hasIndieLicense();
14
32
  }
15
33
 
16
34
 
@@ -52,7 +70,8 @@ function insertNonCommercialUseHint(ctx: IContext) {
52
70
  }
53
71
  }, 100);
54
72
 
55
- logNonCommercialUse();
73
+ if (!hasCommercialLicense())
74
+ logNonCommercialUse();
56
75
 
57
76
  let svg = `<img class="logo" src="${logoSVG}" style="width: 40px; height: 40px; margin-right: 2px; vertical-align: middle; margin-bottom: 2px;"/>`;
58
77
  const logoElement = document.createElement("div");
@@ -66,7 +85,9 @@ function insertNonCommercialUseHint(ctx: IContext) {
66
85
  // textElement.innerHTML = "Needle Engine<br/><span class=\"non-commercial\">Non Commercial</span>";
67
86
  licenseElement.appendChild(textElement);
68
87
 
69
- licenseElement.title = "Needle Engine — non commercial version";
88
+ licenseElement.title = "Needle Engine";
89
+ if (!hasCommercialLicense())
90
+ licenseElement.title += " non commercial version";
70
91
  licenseElement.addEventListener("click", () => {
71
92
  globalThis.open("https://needle.tools", "_blank");
72
93
  });
@@ -56,11 +56,27 @@ export class SceneSwitcher extends Behaviour {
56
56
  @serializable()
57
57
  useSceneLighting: boolean = true;
58
58
 
59
+ /** how many scenes after the currently active scene should be preloaded */
60
+ @serializable()
61
+ preloadNext: number = 1;
62
+
63
+ /** how many scenes before the currently active scene should be preloaded */
64
+ @serializable()
65
+ preloadPrevious: number = 1;
66
+
67
+ /** how many scenes can be loaded in parallel */
68
+ @serializable()
69
+ preloadConcurrent: number = 2;
70
+
71
+
72
+ get currentIndex(): number { return this._currentIndex; }
59
73
 
60
74
  private _currentIndex: number = -1;
61
75
  private _currentScene: AssetReference | undefined = undefined;
62
76
  private _engineElementOverserver: MutationObserver | undefined = undefined;
63
77
 
78
+ private _preloadScheduler?: PreLoadScheduler;
79
+
64
80
  async start() {
65
81
  if (this._currentIndex === -1 && !await this.tryLoadFromQueryParam()) {
66
82
  const value = this.context.domElement.getAttribute(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
@@ -100,6 +116,13 @@ export class SceneSwitcher extends Behaviour {
100
116
  this._engineElementOverserver.observe(this.context.domElement, {
101
117
  attributes: true
102
118
  });
119
+
120
+ if (!this._preloadScheduler)
121
+ this._preloadScheduler = new PreLoadScheduler(this);
122
+ this._preloadScheduler.maxLoadAhead = this.preloadNext;
123
+ this._preloadScheduler.maxLoadBehind = this.preloadPrevious;
124
+ this._preloadScheduler.maxConcurrent = this.preloadConcurrent;
125
+ this._preloadScheduler.begin();
103
126
  }
104
127
 
105
128
  onDisable(): void {
@@ -107,6 +130,7 @@ export class SceneSwitcher extends Behaviour {
107
130
  this.context.input.removeEventListener(InputEvents.KeyDown, this.onKeyDown);
108
131
  this.context.input.removeEventListener(InputEvents.PointerMove, this.onPointerMove);
109
132
  this.context.input.removeEventListener(InputEvents.PointerUp, this.onPointerUp);
133
+ this._preloadScheduler?.stop();
110
134
  }
111
135
 
112
136
  private onPopState = async (_state: PopStateEvent) => {
@@ -189,7 +213,7 @@ export class SceneSwitcher extends Behaviour {
189
213
  select(index: number | string): Promise<boolean> {
190
214
  if (debug) console.log("select", index);
191
215
 
192
- if(typeof index === "object"){
216
+ if (typeof index === "object") {
193
217
  // If a user tries to reference a scene object in a UnityEvent and invoke select(obj)
194
218
  // Then the object will be serialized as a object { guid : ... } or with the index json pointer
195
219
  // This case is not supported right now. Object references in the editor must not be scene references
@@ -271,6 +295,15 @@ export class SceneSwitcher extends Behaviour {
271
295
  return false;
272
296
  }
273
297
 
298
+ preload(index: number) {
299
+ if (index >= 0 && index < this.scenes.length) {
300
+ const scene = this.scenes[index];
301
+ if(scene instanceof AssetReference)
302
+ return scene.preload();
303
+ }
304
+ return couldNotLoadScenePromise;
305
+ }
306
+
274
307
  private tryLoadFromQueryParam() {
275
308
  if (!this.queryParameterName?.length) return couldNotLoadScenePromise;
276
309
  // try restore the scene from the url
@@ -308,3 +341,105 @@ export class SceneSwitcher extends Behaviour {
308
341
  return couldNotLoadScenePromise;
309
342
  }
310
343
  }
344
+
345
+
346
+
347
+
348
+ class PreLoadScheduler {
349
+ maxLoadAhead: number;
350
+ maxLoadBehind: number;
351
+ maxConcurrent: number;
352
+
353
+ private _isRunning: boolean = false;
354
+ private _rooms: SceneSwitcher;
355
+ private _roomTasks: LoadTask[] = [];
356
+ private _maxConcurrentLoads: number = 1;
357
+
358
+ constructor(rooms: SceneSwitcher, ahead: number = 1, behind: number = 1, maxConcurrent: number = 2) {
359
+ this._rooms = rooms;
360
+ this.maxLoadAhead = ahead;
361
+ this.maxLoadBehind = behind;
362
+ this.maxConcurrent = maxConcurrent;
363
+ }
364
+
365
+ begin() {
366
+ if (this._isRunning) return;
367
+ if (debug) console.log("Preload begin")
368
+ this._isRunning = true;
369
+ let lastRoom: number = -1;
370
+ let searchDistance: number;
371
+ let searchCall: number;
372
+ const array = this._rooms.scenes;
373
+ let interval = setInterval(() => {
374
+ if (this.allLoaded()) {
375
+ if (debug)
376
+ console.log("All scenes loaded");
377
+ this.stop();
378
+ }
379
+ if (!this._isRunning) {
380
+ clearInterval(interval);
381
+ return;
382
+ }
383
+ if (this.canLoadNewScene() === false) return;
384
+ if (lastRoom !== this._rooms.currentIndex) {
385
+ lastRoom = this._rooms.currentIndex;
386
+ searchCall = 0;
387
+ searchDistance = 0;
388
+ }
389
+ const searchForward = searchCall % 2 === 0;
390
+ if (searchForward) searchDistance += 1;
391
+ searchCall += 1;
392
+ const maxSearchDistance = searchForward ? this.maxLoadAhead : this.maxLoadBehind;
393
+ if (searchDistance > maxSearchDistance) return;
394
+ let roomIndex = searchForward ? lastRoom + searchDistance : lastRoom - searchDistance;
395
+ if (roomIndex < 0) return;
396
+ // if (roomIndex < 0) roomIndex = array.length + roomIndex;
397
+ if (roomIndex < 0 || roomIndex >= array.length) return;
398
+ const scene = array[roomIndex];
399
+ new LoadTask(roomIndex, scene, this._roomTasks);
400
+ }, 200);
401
+ }
402
+
403
+ stop() {
404
+ this._isRunning = false;
405
+ }
406
+
407
+ canLoadNewScene(): boolean {
408
+ return this._roomTasks.length < this._maxConcurrentLoads;
409
+ }
410
+
411
+ allLoaded(): boolean {
412
+ for (const room of this._rooms.scenes) {
413
+ if (room.isLoaded() === false) return false;
414
+ }
415
+ return true;
416
+ }
417
+ }
418
+
419
+ class LoadTask {
420
+
421
+ index: number;
422
+ asset: AssetReference;
423
+ tasks: LoadTask[];
424
+
425
+ constructor(index: number, asset: AssetReference, tasks: LoadTask[]) {
426
+ this.index = index;
427
+ this.asset = asset;
428
+ this.tasks = tasks;
429
+ tasks.push(this);
430
+ this.awaitLoading();
431
+ }
432
+
433
+ private async awaitLoading() {
434
+ if (!this.asset.isLoaded()) {
435
+ if (debug)
436
+ console.log("Preload start: " + this.asset.uri, this.index);
437
+ await this.asset.preload();
438
+ if (debug)
439
+ console.log("Preload finished: " + this.asset.uri, this.index);
440
+ }
441
+
442
+ const i = this.tasks.indexOf(this);
443
+ if (i >= 0) this.tasks.splice(i, 1);
444
+ }
445
+ }
@@ -179,7 +179,6 @@ export { TriggerModel } from "../export/usdz/extensions/behavior/BehavioursBuild
179
179
  export { UIRaycastUtils } from "../ui/RaycastUtils";
180
180
  export { UIRootComponent } from "../ui/BaseUIComponent";
181
181
  export { UsageMarker } from "../Interactable";
182
- export { USDZBehaviours } from "../export/usdz/extensions/behavior/Behaviour";
183
182
  export { USDZExporter } from "../export/usdz/USDZExporter";
184
183
  export { USDZText } from "../export/usdz/extensions/USDZText";
185
184
  export { VariantAction } from "../export/usdz/extensions/behavior/Actions";
@@ -48,7 +48,6 @@ class USDObject {
48
48
  parent: USDObject | null;
49
49
  children: Array<USDObject | null> = [];
50
50
  _eventListeners: {};
51
- mesh: any;
52
51
 
53
52
  static createEmptyParent( object ) {
54
53
 
@@ -92,7 +91,7 @@ class USDObject {
92
91
 
93
92
  clone() {
94
93
 
95
- const clone = new USDObject( MathUtils.generateUUID(), this.name, this.matrix, this.mesh, this.material );
94
+ const clone = new USDObject( MathUtils.generateUUID(), this.name, this.matrix, this.geometry, this.material );
96
95
  clone.isDynamic = this.isDynamic;
97
96
  return clone;
98
97
 
@@ -383,9 +382,20 @@ class USDZExporterContext {
383
382
 
384
383
  }
385
384
 
385
+ /**[documentation](https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/preliminary_anchoringapi/preliminary_anchoring_type) */
386
+ export type Anchoring = "plane" | "image" | "face" | "none"
387
+ /**[documentation](https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/preliminary_anchoringapi/preliminary_planeanchoring_alignment) */
388
+ export type Alignment = "horizontal" | "vertical" | "any";
389
+
386
390
  class USDZExporterOptions {
387
- ar: { anchoring: { type: string } } = { anchoring: { type: 'plane' } };
388
- planeAnchoring: { alignment: string } = { alignment: 'horizontal' };
391
+ ar: {
392
+ anchoring: { type: Anchoring },
393
+ planeAnchoring: { alignment: Alignment },
394
+ } = {
395
+ anchoring: { type: 'plane' },
396
+ planeAnchoring: { alignment: 'horizontal' }
397
+ };
398
+ quickLookCompatible: boolean = false;
389
399
  extensions: any[] = [];
390
400
  }
391
401
 
@@ -594,7 +604,7 @@ function parseDocument( context: USDZExporterContext ) {
594
604
 
595
605
  writer.appendLine( `token preliminary:anchoring:type = "${context.exporter.sceneAnchoringOptions.ar.anchoring.type}"` );
596
606
  if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'plane')
597
- writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.planeAnchoring.alignment}"` );
607
+ writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.ar.planeAnchoring.alignment}"` );
598
608
  // 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.
599
609
  if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'image')
600
610
  writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
@@ -1076,7 +1086,7 @@ function buildMaterial( material: MeshStandardMaterial, textures ) {
1076
1086
  const pad = ' ';
1077
1087
  const inputs: Array<string> = [];
1078
1088
  const samplers: Array<string> = [];
1079
- const exportForQuickLook = true;
1089
+ const exportForQuickLook = false;
1080
1090
 
1081
1091
  function buildTexture( texture, mapType, color: Color | undefined = undefined, opacity: number | undefined = undefined ) {
1082
1092
 
@@ -1088,6 +1098,11 @@ function buildMaterial( material: MeshStandardMaterial, textures ) {
1088
1098
 
1089
1099
  const repeat = texture.repeat.clone();
1090
1100
  const offset = texture.offset.clone();
1101
+ const rotation = texture.rotation;
1102
+
1103
+ // rotation is around the wrong point. after rotation we need to shift offset again so that we're rotating around the right spot
1104
+ let xRotationOffset = Math.sin(rotation);
1105
+ let yRotationOffset = Math.cos(rotation);
1091
1106
 
1092
1107
  // texture coordinates start in the opposite corner, need to correct
1093
1108
  offset.y = 1 - offset.y - repeat.y;
@@ -1096,15 +1111,28 @@ function buildMaterial( material: MeshStandardMaterial, textures ) {
1096
1111
  // Apple Feedback: FB10036297 and FB11442287
1097
1112
  if ( exportForQuickLook ) {
1098
1113
 
1114
+ // This is NOT correct yet in QuickLook, but comes close for a range of models.
1115
+ // It becomes more incorrect the bigger the offset is
1116
+
1099
1117
  offset.x = offset.x / repeat.x;
1100
1118
  offset.y = offset.y / repeat.y;
1101
1119
 
1120
+ offset.x += xRotationOffset / repeat.x;
1121
+ offset.y += yRotationOffset - 1;
1122
+ }
1123
+
1124
+ else {
1125
+
1126
+ // results match glTF results exactly. verified correct in usdview.
1127
+ offset.x += xRotationOffset * repeat.x;
1128
+ offset.y += (1 - yRotationOffset) * repeat.y;
1129
+
1102
1130
  }
1103
1131
 
1104
1132
  textures[ id ] = texture;
1105
1133
  const uvReader = mapType == 'occlusion' ? 'uvReader_st2' : 'uvReader_st';
1106
1134
 
1107
- const needsTextureTransform = ( repeat.x != 1 || repeat.y != 1 || offset.x != 0 || offset.y != 0 );
1135
+ const needsTextureTransform = ( repeat.x != 1 || repeat.y != 1 || offset.x != 0 || offset.y != 0 || rotation != 0 );
1108
1136
  const textureTransformInput = `</Materials/Material_${material.id}/${uvReader}.outputs:result>`;
1109
1137
  const textureTransformOutput = `</Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`;
1110
1138
 
@@ -1123,6 +1151,7 @@ function buildMaterial( material: MeshStandardMaterial, textures ) {
1123
1151
  float2 inputs:in.connect = ${textureTransformInput}
1124
1152
  float2 inputs:scale = ${buildVector2( repeat )}
1125
1153
  float2 inputs:translation = ${buildVector2( offset )}
1154
+ float inputs:rotation = ${(rotation / Math.PI * 180).toFixed( PRECISION )}
1126
1155
  float2 outputs:result
1127
1156
  }
1128
1157
  ` : '' }
@@ -13,6 +13,7 @@ import { showBalloonMessage, showBalloonWarning } from "../../../engine/debug/de
13
13
  import { Context } from "../../../engine/engine_setup";
14
14
  import { WebARSessionRoot } from "../../webxr/WebARSessionRoot";
15
15
  import { hasProLicense } from "../../../engine/engine_license";
16
+ import { BehaviorExtension } from "./extensions/behavior/Behaviour";
16
17
 
17
18
  const debug = getParam("debugusdz");
18
19
 
@@ -51,6 +52,9 @@ export class USDZExporter extends Behaviour {
51
52
  @serializable()
52
53
  planeAnchoringAlignment: "horizontal" | "vertical" | "any" = "horizontal";
53
54
 
55
+ @serializable()
56
+ interactive: boolean = true;
57
+
54
58
  extensions: IUSDExporterExtension[] = [];
55
59
 
56
60
  private link!: HTMLAnchorElement;
@@ -84,9 +88,11 @@ export class USDZExporter extends Behaviour {
84
88
  this.objectToExport = this.gameObject;
85
89
  if (!this.objectToExport?.children?.length && !(this.objectToExport as Mesh)?.isMesh)
86
90
  this.objectToExport = this.context.scene;
87
- }
88
-
89
91
 
92
+ if (this.interactive) {
93
+ this.extensions.push(new BehaviorExtension());
94
+ }
95
+ }
90
96
 
91
97
  onEnable() {
92
98
  const ios = isiOS()
@@ -157,12 +163,13 @@ export class USDZExporter extends Behaviour {
157
163
  ar: {
158
164
  anchoring: {
159
165
  type: this.anchoringType,
160
- }
161
- },
162
- planeAnchoring: {
163
- alignment: this.planeAnchoringAlignment,
166
+ },
167
+ planeAnchoring: {
168
+ alignment: this.planeAnchoringAlignment,
169
+ },
164
170
  },
165
- extensions: extensions
171
+ extensions: extensions,
172
+ quickLookCompatible: true,
166
173
  });
167
174
  const blob = new Blob([arraybuffer], { type: 'application/octet-stream' });
168
175
 
@@ -1,9 +1,8 @@
1
- import { Behaviour, GameObject } from "../../../../Component";
2
- import { USDZExporter } from "../../USDZExporter";
1
+ import { GameObject } from "../../../../Component";
2
+ import { IContext } from "../../../../../engine/engine_types";
3
3
  import { IUSDExporterExtension } from "../../Extension";
4
4
  import { USDObject, USDWriter } from "../../ThreeUSDZExporter";
5
5
  import { BehaviorModel } from "./BehavioursBuilder";
6
- import { IContext } from "../../../../../engine/engine_types";
7
6
 
8
7
  export interface UsdzBehaviour {
9
8
  createBehaviours?(ext: BehaviorExtension, model: USDObject, context: IContext): void;
@@ -11,15 +10,6 @@ export interface UsdzBehaviour {
11
10
  afterCreateDocument?(ext: BehaviorExtension, context: IContext): void;
12
11
  }
13
12
 
14
- export class USDZBehaviours extends Behaviour {
15
- start() {
16
- const exporter = GameObject.findObjectOfType(USDZExporter);
17
- if (exporter) {
18
- exporter.extensions.push(new BehaviorExtension());
19
- }
20
- }
21
- }
22
-
23
13
  export class BehaviorExtension implements IUSDExporterExtension {
24
14
 
25
15
  get extensionName(): string {
@@ -40,8 +30,8 @@ export class BehaviorExtension implements IUSDExporterExtension {
40
30
  GameObject.foreachComponent(e, (comp) => {
41
31
  const c = comp as unknown as UsdzBehaviour;
42
32
  if (
43
- typeof c.createBehaviours === "function" ||
44
- typeof c.beforeCreateDocument === "function" ||
33
+ typeof c.createBehaviours === "function" ||
34
+ typeof c.beforeCreateDocument === "function" ||
45
35
  typeof c.afterCreateDocument === "function"
46
36
  ) {
47
37
  this.behaviourComponents.push(c);
@@ -66,7 +56,7 @@ export class BehaviorExtension implements IUSDExporterExtension {
66
56
  this.behaviourComponents.length = 0;
67
57
  }
68
58
 
69
- onAfterHierarchy(context, writer : USDWriter) {
59
+ onAfterHierarchy(context, writer: USDWriter) {
70
60
  if (this.behaviours?.length) {
71
61
 
72
62
  // this.combineBehavioursWithSameTapActions();
@@ -7,7 +7,7 @@ import { RegisteredAnimationInfo, UsdzAnimation } from "../Animation";
7
7
  import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../../../../../engine/engine_three_utils";
8
8
 
9
9
  import { Object3D, Material, Vector3, Quaternion, AnimationAction } from "three";
10
- import { USDObject } from "../../ThreeUSDZExporter";
10
+ import { USDDocument, USDObject } from "../../ThreeUSDZExporter";
11
11
 
12
12
  import { BehaviorExtension, UsdzBehaviour } from "./Behaviour";
13
13
  import { ActionBuilder, ActionModel, BehaviorModel, IBehaviorElement, MotionType, Space, TriggerBuilder } from "./BehavioursBuilder";
@@ -44,7 +44,7 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
44
44
 
45
45
  const thisScale = getWorldScale(this.object).clone();
46
46
  const targetScale = getWorldScale(this.target).clone();
47
-
47
+
48
48
  const dist = thisPos.distanceTo(targetPos);
49
49
  const rotDist = thisRot.angleTo(targetRot);
50
50
  const scaleDist = thisScale.distanceTo(targetScale);
@@ -63,10 +63,10 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
63
63
 
64
64
  t01 += this.context.time.deltaTime / this.duration;
65
65
  if (t01 > 1) t01 = 1;
66
-
66
+
67
67
  // apply ease-in-out
68
68
  // https://easings.net/
69
- eased = t01 < 0.5 ? 4 * t01 * t01 * t01 : 1 - Math.pow(-2 * t01 + 2, 3) / 2;
69
+ eased = t01 < 0.5 ? 4 * t01 * t01 * t01 : 1 - Math.pow(-2 * t01 + 2, 3) / 2;
70
70
 
71
71
  this.targetPos.lerpVectors(thisPos, targetPos, eased);
72
72
  this.targetRot.slerpQuaternions(thisRot, targetRot, eased);
@@ -83,7 +83,7 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
83
83
  }
84
84
 
85
85
  private *moveRelative() {
86
-
86
+
87
87
  if (!this.target || !this.object) return;
88
88
 
89
89
  const thisPos = this.object.position.clone();
@@ -107,10 +107,10 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
107
107
 
108
108
  t01 += this.context.time.deltaTime / this.duration;
109
109
  if (t01 > 1) t01 = 1;
110
-
110
+
111
111
  // apply ease-in-out
112
112
  // https://easings.net/
113
- eased = t01 < 0.5 ? 4 * t01 * t01 * t01 : 1 - Math.pow(-2 * t01 + 2, 3) / 2;
113
+ eased = t01 < 0.5 ? 4 * t01 * t01 * t01 : 1 - Math.pow(-2 * t01 + 2, 3) / 2;
114
114
 
115
115
  this.object.position.lerpVectors(thisPos, this.targetPos, eased);
116
116
  this.object.quaternion.slerpQuaternions(thisRot, this.targetRot, eased);
@@ -120,7 +120,7 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
120
120
  }
121
121
 
122
122
  this.coroutine = null;
123
- }
123
+ }
124
124
 
125
125
  onPointerClick() {
126
126
  if (this.coroutine) this.stopCoroutine(this.coroutine);
@@ -313,11 +313,12 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
313
313
  hideClickedObject = true;
314
314
  targetState = !this.target.visible;
315
315
 
316
- // TODO check where we have to create the clone; here it doesn't show up
316
+ if (!this.selfModel.parent || this.selfModel.parent.isEmpty())
317
+ USDDocument.createEmptyParent(this.selfModel);
318
+
317
319
  this.toggleModel = this.selfModel.clone();
318
320
  this.toggleModel.name += "_toggle";
319
- if (this.selfModel.parent)
320
- this.selfModel.parent.add(this.toggleModel);
321
+ this.selfModel.parent!.add(this.toggleModel);
321
322
  }
322
323
 
323
324
  const sequence: ActionModel[] = [];
@@ -344,8 +345,8 @@ export class SetActiveOnClick extends Behaviour implements IPointerClickHandler,
344
345
  ActionBuilder.sequence(...toggleSequence)
345
346
  ));
346
347
 
347
- ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
348
- TriggerBuilder.sceneStartTrigger(),
348
+ ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
349
+ TriggerBuilder.sceneStartTrigger(),
349
350
  ActionBuilder.fadeAction(this.toggleModel, 0, false)
350
351
  ));
351
352
  }
@@ -363,8 +364,8 @@ export class HideOnStart extends Behaviour implements UsdzBehaviour {
363
364
 
364
365
  createBehaviours(ext, model, _context) {
365
366
  if (model.uuid === this.gameObject.uuid)
366
- ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
367
- TriggerBuilder.sceneStartTrigger(),
367
+ ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
368
+ TriggerBuilder.sceneStartTrigger(),
368
369
  ActionBuilder.fadeAction(model, 0, false)
369
370
  ));
370
371
  }
@@ -394,8 +395,7 @@ export class EmphasizeOnClick extends Behaviour implements UsdzBehaviour {
394
395
  createBehaviours(ext, model, _context) {
395
396
  if (!this.target) return;
396
397
 
397
- if (model.uuid === this.gameObject.uuid)
398
- {
398
+ if (model.uuid === this.gameObject.uuid) {
399
399
  const emphasize = new BehaviorModel("emphasize " + this.name,
400
400
  TriggerBuilder.tapTrigger(this.gameObject),
401
401
  ActionBuilder.emphasize(this.target, this.duration, this.motionType, undefined, "basic"),
@@ -417,7 +417,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
417
417
 
418
418
  @serializable()
419
419
  stateName?: string;
420
-
420
+
421
421
  @serializable()
422
422
  stateNameAfterPlaying?: string;
423
423
 
@@ -486,7 +486,7 @@ export class PlayAnimationOnClick extends Behaviour implements IPointerClickHand
486
486
 
487
487
  createAnimation(ext, model, _context) {
488
488
  if (this.target && this.animator) {
489
-
489
+
490
490
  const state = this.animator?.runtimeAnimatorController?.findState(this.stateName);
491
491
  this.stateAnimationModel = model;
492
492
  this.stateAnimation = ext.registerAnimation(this.target, state?.motion.clip);
@@ -74,7 +74,7 @@ function resolve(targetObject: Target, document: USDDocument): string {
74
74
  let obj = targetObject[i];
75
75
  if (typeof obj === "string")
76
76
  str += obj;
77
- else if ( typeof obj === "object") {
77
+ else if (typeof obj === "object") {
78
78
  //@ts-ignore
79
79
  if (obj.isObject3D) {
80
80
  //@ts-ignore
@@ -436,7 +436,7 @@ export class ActionBuilder {
436
436
  act.tokenId = "Emphasize";
437
437
  act.duration = duration;
438
438
  act.style = style ?? "basic";
439
- act.motionType = MotionType[motionType];
439
+ act.motionType = MotionType[motionType];
440
440
  act.moveDistance = moveDistance;
441
441
  return act;
442
442
  }