@needle-tools/engine 2.52.0-pre → 2.53.0-pre

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 (98) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/needle-engine.d.ts +187 -36
  3. package/dist/needle-engine.js +385 -385
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +40 -40
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/engine_components.js +2 -2
  8. package/lib/engine/engine_components.js.map +1 -1
  9. package/lib/engine/engine_networking_instantiate.d.ts +1 -1
  10. package/lib/engine/engine_networking_instantiate.js +3 -0
  11. package/lib/engine/engine_networking_instantiate.js.map +1 -1
  12. package/lib/engine/engine_serialization_builtin_serializer.d.ts +6 -0
  13. package/lib/engine/engine_serialization_builtin_serializer.js +31 -6
  14. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  15. package/lib/engine/engine_serialization_core.d.ts +1 -0
  16. package/lib/engine/engine_serialization_core.js +6 -0
  17. package/lib/engine/engine_serialization_core.js.map +1 -1
  18. package/lib/engine/engine_setup.js +2 -2
  19. package/lib/engine/engine_setup.js.map +1 -1
  20. package/lib/engine/engine_texture.d.ts +3 -0
  21. package/lib/engine/engine_texture.js +4 -0
  22. package/lib/engine/engine_texture.js.map +1 -0
  23. package/lib/engine/extensions/{NEEDLE_deferred_texture.d.ts → NEEDLE_progressive.d.ts} +2 -2
  24. package/lib/engine/extensions/{NEEDLE_deferred_texture.js → NEEDLE_progressive.js} +6 -6
  25. package/lib/engine/extensions/NEEDLE_progressive.js.map +1 -0
  26. package/lib/engine/extensions/extensions.js +2 -2
  27. package/lib/engine/extensions/extensions.js.map +1 -1
  28. package/lib/engine-components/Camera.js +17 -2
  29. package/lib/engine-components/Camera.js.map +1 -1
  30. package/lib/engine-components/Light.js +1 -0
  31. package/lib/engine-components/Light.js.map +1 -1
  32. package/lib/engine-components/OrbitControls.js +6 -2
  33. package/lib/engine-components/OrbitControls.js.map +1 -1
  34. package/lib/engine-components/Renderer.js +3 -3
  35. package/lib/engine-components/Renderer.js.map +1 -1
  36. package/lib/engine-components/Voip.js +13 -4
  37. package/lib/engine-components/Voip.js.map +1 -1
  38. package/lib/engine-components/codegen/components.d.ts +6 -2
  39. package/lib/engine-components/codegen/components.js +6 -2
  40. package/lib/engine-components/codegen/components.js.map +1 -1
  41. package/lib/engine-components/export/{GltfExport.d.ts → gltf/GltfExport.d.ts} +2 -2
  42. package/lib/engine-components/export/{GltfExport.js → gltf/GltfExport.js} +7 -7
  43. package/lib/engine-components/export/gltf/GltfExport.js.map +1 -0
  44. package/lib/engine-components/export/usdz/Extension.d.ts +9 -0
  45. package/lib/engine-components/export/usdz/Extension.js +2 -0
  46. package/lib/engine-components/export/usdz/Extension.js.map +1 -0
  47. package/lib/engine-components/export/usdz/USDZExporter.d.ts +25 -0
  48. package/lib/engine-components/export/usdz/USDZExporter.js +193 -0
  49. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -0
  50. package/lib/engine-components/export/usdz/extensions/Animation.d.ts +44 -0
  51. package/lib/engine-components/export/usdz/extensions/Animation.js +264 -0
  52. package/lib/engine-components/export/usdz/extensions/Animation.js.map +1 -0
  53. package/lib/engine-components/export/usdz/types.d.ts +34 -0
  54. package/lib/engine-components/export/usdz/types.js +2 -0
  55. package/lib/engine-components/export/usdz/types.js.map +1 -0
  56. package/lib/engine-components/export/usdz/utils/animationutils.d.ts +3 -0
  57. package/lib/engine-components/export/usdz/utils/animationutils.js +46 -0
  58. package/lib/engine-components/export/usdz/utils/animationutils.js.map +1 -0
  59. package/lib/engine-components/export/usdz/utils/quicklook.d.ts +2 -0
  60. package/lib/engine-components/export/usdz/utils/quicklook.js +36 -0
  61. package/lib/engine-components/export/usdz/utils/quicklook.js.map +1 -0
  62. package/lib/engine-components/export/usdz/utils/timeutils.d.ts +1 -0
  63. package/lib/engine-components/export/usdz/utils/timeutils.js +15 -0
  64. package/lib/engine-components/export/usdz/utils/timeutils.js.map +1 -0
  65. package/lib/engine-components/ui/Graphic.d.ts +2 -0
  66. package/lib/engine-components/ui/Graphic.js +15 -0
  67. package/lib/engine-components/ui/Graphic.js.map +1 -1
  68. package/lib/engine-components/ui/Utils.d.ts +2 -1
  69. package/lib/engine-components/ui/Utils.js +5 -3
  70. package/lib/engine-components/ui/Utils.js.map +1 -1
  71. package/package.json +2 -2
  72. package/src/engine/codegen/register_types.js +14 -6
  73. package/src/engine/engine_components.ts +2 -2
  74. package/src/engine/engine_networking_instantiate.ts +4 -1
  75. package/src/engine/engine_serialization_builtin_serializer.ts +35 -7
  76. package/src/engine/engine_serialization_core.ts +7 -1
  77. package/src/engine/engine_setup.ts +2 -2
  78. package/src/engine/engine_texture.ts +6 -0
  79. package/src/engine/extensions/{NEEDLE_deferred_texture.ts → NEEDLE_progressive.ts} +9 -11
  80. package/src/engine/extensions/extensions.ts +2 -2
  81. package/src/engine-components/Camera.ts +17 -3
  82. package/src/engine-components/Light.ts +3 -1
  83. package/src/engine-components/OrbitControls.ts +7 -3
  84. package/src/engine-components/Renderer.ts +3 -3
  85. package/src/engine-components/Voip.ts +14 -4
  86. package/src/engine-components/codegen/components.ts +6 -2
  87. package/src/engine-components/export/{GltfExport.ts → gltf/GltfExport.ts} +7 -7
  88. package/src/engine-components/export/usdz/Extension.ts +12 -0
  89. package/src/engine-components/export/usdz/USDZExporter.ts +216 -0
  90. package/src/engine-components/export/usdz/extensions/Animation.ts +306 -0
  91. package/src/engine-components/export/usdz/types.ts +39 -0
  92. package/src/engine-components/export/usdz/utils/animationutils.ts +60 -0
  93. package/src/engine-components/export/usdz/utils/quicklook.ts +43 -0
  94. package/src/engine-components/export/usdz/utils/timeutils.ts +20 -0
  95. package/src/engine-components/ui/Graphic.ts +15 -1
  96. package/src/engine-components/ui/Utils.ts +5 -3
  97. package/lib/engine/extensions/NEEDLE_deferred_texture.js.map +0 -1
  98. package/lib/engine-components/export/GltfExport.js.map +0 -1
@@ -5,14 +5,13 @@ import { Context } from "../engine_setup";
5
5
  import { addDracoAndKTX2Loaders } from "../engine_loaders";
6
6
  import { getParam, getPath } from "../engine_utils";
7
7
 
8
- export const EXTENSION_NAME = "NEEDLE_deferred_texture";
8
+ export const EXTENSION_NAME = "NEEDLE_progressive";
9
9
 
10
10
  const debug = getParam("debugprogressive");
11
11
 
12
- declare type DeferredTextureModel = {
12
+ declare type ProgressiveTextureSchema = {
13
13
  uri: string;
14
14
  guid: string;
15
- usage?: string,
16
15
  }
17
16
 
18
17
  const debug_toggle_maps: Map<Material, { [key: string]: { original: Texture, lod0: Texture } }> = new Map();
@@ -35,8 +34,7 @@ if (debug) {
35
34
  });
36
35
  }
37
36
 
38
- export class NEEDLE_deferred_texture implements GLTFLoaderPlugin {
39
-
37
+ export class NEEDLE_progressive implements GLTFLoaderPlugin {
40
38
 
41
39
  static assignTextureLOD(context: Context, source: SourceIdentifier | undefined, material: Material, level: number = 0) {
42
40
  if (!material) return;
@@ -46,7 +44,7 @@ export class NEEDLE_deferred_texture implements GLTFLoaderPlugin {
46
44
 
47
45
  if (debug) console.log("-----------\n", "FIND", material.name, slot, val?.name, val?.userData, val, material);
48
46
 
49
- NEEDLE_deferred_texture.getOrLoadTexture(context, source, material, slot, val, level).then(t => {
47
+ NEEDLE_progressive.getOrLoadTexture(context, source, material, slot, val, level).then(t => {
50
48
  if (t?.isTexture === true) {
51
49
 
52
50
  if (debug) console.log("Assign LOD", material.name, slot, t.name, t["guid"], material, "Prev:", val, "Now:", t, "\n--------------");
@@ -109,14 +107,14 @@ export class NEEDLE_deferred_texture implements GLTFLoaderPlugin {
109
107
  console.log("AFTER", this.sourceId, gltf);
110
108
  this.parser.json.textures?.forEach((textureInfo, index) => {
111
109
  if (textureInfo?.extensions) {
112
- const ext: DeferredTextureModel = textureInfo?.extensions[EXTENSION_NAME];
110
+ const ext: ProgressiveTextureSchema = textureInfo?.extensions[EXTENSION_NAME];
113
111
  if (ext) {
114
112
  const prom = this.parser.getDependency("texture", index);
115
113
  this._loading.splice(this._loading.indexOf(index), 1);
116
114
  prom.then(t => {
117
115
  if (debug) console.log("register texture", t.name, t.uuid, ext);
118
116
  t.userData.deferred = ext;
119
- NEEDLE_deferred_texture.cache.set(t.uuid, ext);
117
+ NEEDLE_progressive.cache.set(t.uuid, ext);
120
118
  });
121
119
  }
122
120
  }
@@ -125,13 +123,13 @@ export class NEEDLE_deferred_texture implements GLTFLoaderPlugin {
125
123
  return null;
126
124
  }
127
125
 
128
- private static cache = new Map<string, DeferredTextureModel>();
126
+ private static cache = new Map<string, ProgressiveTextureSchema>();
129
127
  private static resolved: { [key: string]: Texture } = {};
130
128
 
131
129
  private static async getOrLoadTexture(context: Context, source: SourceIdentifier | undefined, material: Material, slot: string, current: Texture, _level: number): Promise<Texture | null> {
132
130
 
133
131
  const key = current.uuid;
134
- const ext: DeferredTextureModel | undefined = NEEDLE_deferred_texture.cache.get(key);// || current.userData.deferred;
132
+ const ext: ProgressiveTextureSchema | undefined = NEEDLE_progressive.cache.get(key);// || current.userData.deferred;
135
133
  if (ext) {
136
134
  if (debug)
137
135
  console.log(key, ext.uri, ext.guid);
@@ -158,7 +156,7 @@ export class NEEDLE_deferred_texture implements GLTFLoaderPlugin {
158
156
  for (const tex of gltf.parser.json.textures) {
159
157
  index++;
160
158
  if (tex?.extensions) {
161
- const other: DeferredTextureModel = tex?.extensions[EXTENSION_NAME];
159
+ const other: ProgressiveTextureSchema = tex?.extensions[EXTENSION_NAME];
162
160
  if (other?.guid) {
163
161
  if (other.guid === ext.guid) {
164
162
  found = true;
@@ -12,7 +12,7 @@ import { SourceIdentifier } from "../engine_types";
12
12
  import { Context } from "../engine_setup";
13
13
  import { NEEDLE_lighting_settings } from "./NEEDLE_lighting_settings";
14
14
  import { NEEDLE_render_objects } from "./NEEDLE_render_objects";
15
- import { NEEDLE_deferred_texture } from "./NEEDLE_deferred_texture";
15
+ import { NEEDLE_progressive } from "./NEEDLE_progressive";
16
16
 
17
17
  export function registerComponentExtension(loader: GLTFLoader): NEEDLE_components {
18
18
  const ext = new NEEDLE_components();
@@ -40,7 +40,7 @@ export function registerExtensions(loader: GLTFLoader, context: Context, sourceI
40
40
  loader.register(p => new NEEDLE_lighting_settings(p, sourceId, context));
41
41
  loader.register(p => new NEEDLE_techniques_webgl(p, sourceId));
42
42
  loader.register(p => new NEEDLE_render_objects(p, sourceId));
43
- loader.register(p => new NEEDLE_deferred_texture(p, sourceId, context));
43
+ loader.register(p => new NEEDLE_progressive(p, sourceId, context));
44
44
  loader.register(p => new EXT_texture_exr(p));
45
45
 
46
46
  const setPointerResolverFunction = loader["setAnimationPointerResolver"];
@@ -9,6 +9,7 @@ import { XRSessionMode } from "../engine/engine_setup";
9
9
  import { ICamera } from "../engine/engine_types"
10
10
  import { showBalloonMessage } from "../engine/debug/debug";
11
11
  import { getWorldPosition } from "../engine/engine_three_utils";
12
+ import { Gizmos } from "../engine/engine_gizmos";
12
13
 
13
14
  export enum ClearFlags {
14
15
  Skybox = 1,
@@ -17,6 +18,7 @@ export enum ClearFlags {
17
18
  }
18
19
 
19
20
  const debug = getParam("debugcam");
21
+ const debugscreenpointtoray = getParam("debugscreenpointtoray");
20
22
 
21
23
  export class Camera extends Behaviour implements ICamera {
22
24
 
@@ -157,14 +159,15 @@ export class Camera extends Behaviour implements ICamera {
157
159
  private static _origin: THREE.Vector3 = new THREE.Vector3();
158
160
  private static _direction: THREE.Vector3 = new THREE.Vector3();
159
161
  public screenPointToRay(x: number, y: number, ray?: Ray): Ray {
162
+ let cam = this.cam;
160
163
  const origin = Camera._origin;
161
164
  origin.set(x, y, -1);
162
165
  this.context.input.convertScreenspaceToRaycastSpace(origin);
166
+ if(debugscreenpointtoray) console.log("screenPointToRay", x.toFixed(2), y.toFixed(2), "now:", origin.x.toFixed(2), origin.y.toFixed(2), "isInXR:" + this.context.isInXR);
163
167
  origin.z = -1;
164
- origin.unproject(this.cam);
165
-
168
+ origin.unproject(cam);
166
169
  const dir = Camera._direction.set(origin.x, origin.y, origin.z);
167
- const camPosition = getWorldPosition(this.cam);
170
+ const camPosition = getWorldPosition(cam);
168
171
  dir.sub(camPosition);
169
172
  dir.normalize();
170
173
  if (ray) {
@@ -180,6 +183,17 @@ export class Camera extends Behaviour implements ICamera {
180
183
  if (!this.sourceId) {
181
184
  console.warn("Camera has no source - the camera should be exported inside a gltf", this.name);
182
185
  }
186
+
187
+ if(debugscreenpointtoray){
188
+ window.addEventListener("pointerdown", evt => {
189
+ const px = evt.clientX;
190
+ const py = evt.clientY;
191
+ console.log("touch", px.toFixed(2), py.toFixed(2))
192
+ const ray = this.screenPointToRay(px, py);
193
+ const randomHex = "#" + Math.floor(Math.random()*16777215).toString(16);
194
+ Gizmos.DrawRay(ray.origin, ray.direction, randomHex, 10);
195
+ });
196
+ }
183
197
  }
184
198
 
185
199
  onEnable(): void {
@@ -292,6 +292,7 @@ export class Light extends Behaviour implements ILight {
292
292
 
293
293
  if (lightAlreadyCreated && !this.light) {
294
294
  this.light = this.gameObject as unknown as THREE.Light;
295
+ this._intensity = this.light.intensity;
295
296
 
296
297
  switch (this.type) {
297
298
  case LightType.Directional:
@@ -346,7 +347,8 @@ export class Light extends Behaviour implements ILight {
346
347
  if (this.light) {
347
348
  if (this._intensity >= 0)
348
349
  this.light.intensity = this._intensity;
349
- else this._intensity = this.light.intensity;
350
+ else
351
+ this._intensity = this.light.intensity;
350
352
 
351
353
  if (this.shadows !== LightShadows.None) {
352
354
  this.light.castShadow = true;
@@ -11,6 +11,9 @@ import { getParam, isMobileDevice } from "../engine/engine_utils";
11
11
 
12
12
  const freeCam = getParam("freecam");
13
13
 
14
+ const disabledKeys = { LEFT: "", UP: "", RIGHT: "", BOTTOM: "" };
15
+ let defaultKeys: any = undefined;
16
+
14
17
  export class OrbitControls extends Behaviour {
15
18
  public get controls() {
16
19
  return this._controls;
@@ -73,6 +76,7 @@ export class OrbitControls extends Behaviour {
73
76
  if (cam)
74
77
  this._cameraObject = cam;
75
78
  this._controls = new ThreeOrbitControls(cam!, this.context.renderer.domElement);
79
+ if (defaultKeys === undefined) defaultKeys = { ...this._controls.keys };
76
80
  }
77
81
 
78
82
  if (this._controls) {
@@ -80,12 +84,11 @@ export class OrbitControls extends Behaviour {
80
84
  this.enablePan = true;
81
85
  this.enableZoom = true;
82
86
  this.middleClickToFocus = true;
83
- if(isMobileDevice()) this.doubleClickToFocus = true;
87
+ if (isMobileDevice()) this.doubleClickToFocus = true;
84
88
  }
85
89
 
86
90
  this._controls.enableDamping = this.enableDamping;
87
- //@ts-ignore (not in types)
88
- this._controls.enableKeys = this.enableKeys;
91
+ this._controls.keys = this.enableKeys ? defaultKeys : disabledKeys;
89
92
  this._controls.autoRotate = this.autoRotate;
90
93
  this._controls.autoRotateSpeed = this.autoRotateSpeed;
91
94
  this._controls.enableZoom = this.enableZoom;
@@ -164,6 +167,7 @@ export class OrbitControls extends Behaviour {
164
167
  this._inputs += 1;
165
168
  }
166
169
  if (this._inputs > 0) {
170
+ this.autoRotate = false;
167
171
  this._controls.autoRotate = false;
168
172
  this._lerpCameraToTarget = false;
169
173
  this._lerpToTargetPosition = false;
@@ -7,7 +7,7 @@ import { getParam } from "../engine/engine_utils";
7
7
  import { serializable } from "../engine/engine_serialization_decorator";
8
8
  import { AxesHelper, Material, Mesh, Object3D, SkinnedMesh, Texture, Vector4 } from "three";
9
9
  import { NEEDLE_render_objects } from "../engine/extensions/NEEDLE_render_objects";
10
- import { NEEDLE_deferred_texture } from "../engine/extensions/NEEDLE_deferred_texture";
10
+ import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive";
11
11
  import { NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing";
12
12
  import { IRenderer, ISharedMaterials } from "../engine/engine_types";
13
13
  import { debug, ReflectionProbe } from "./ReflectionProbe";
@@ -465,11 +465,11 @@ export class Renderer extends Behaviour implements IRenderer {
465
465
  if (debugProgressiveLoading) {
466
466
  console.log("Load material LOD (with delay)", material.name);
467
467
  setTimeout(() => {
468
- NEEDLE_deferred_texture.assignTextureLOD(this.context, this.sourceId, material);
468
+ NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, material);
469
469
  }, 2000);
470
470
  }
471
471
  else {
472
- NEEDLE_deferred_texture.assignTextureLOD(this.context, this.sourceId, material);
472
+ NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, material);
473
473
  }
474
474
  }
475
475
 
@@ -307,6 +307,11 @@ export class Voip extends Behaviour {
307
307
  return;
308
308
  }
309
309
 
310
+ if (utils.isiOS() && utils.isSafari()) {
311
+ console.log("VOIP is currently not supported on Safari iOS")
312
+ return;
313
+ }
314
+
310
315
  this.peer = new Peer();
311
316
  navigator["getUserMedia"] = (navigator["getUserMedia"] || navigator["webkitGetUserMedia"] || navigator["mozGetUserMedia"] || navigator["msGetUserMedia"]);
312
317
 
@@ -360,10 +365,15 @@ export class Voip extends Behaviour {
360
365
  onDisable(): void {
361
366
  console.log("TODO: close all");
362
367
  for (const key in this.currentIncomingCalls) {
363
- const call = this.currentIncomingCalls[key];
364
- call?.close();
365
- const con = this.connections[key];
366
- con?.close();
368
+ try {
369
+ const call = this.currentIncomingCalls[key];
370
+ call?.close();
371
+ const con = this.connections[key];
372
+ con?.close();
373
+ }
374
+ catch (err) {
375
+ console.error(err);
376
+ }
367
377
  }
368
378
  }
369
379
 
@@ -123,8 +123,6 @@ export { Avatar_Brain_LookAt } from "../avatar/Avatar_Brain_LookAt";
123
123
  export { Avatar_MouthShapes } from "../avatar/Avatar_MouthShapes";
124
124
  export { Avatar_MustacheShake } from "../avatar/Avatar_MustacheShake";
125
125
  export { LogStats } from "../debug/LogStats";
126
- export { GltfExportBox } from "../export/GltfExport";
127
- export { GltfExport } from "../export/GltfExport";
128
126
  export { RGBAColor } from "../js-extensions/RGBAColor";
129
127
  export { PlayableDirector } from "../timeline/PlayableDirector";
130
128
  export { SignalAsset } from "../timeline/SignalAsset";
@@ -160,3 +158,9 @@ export { Rect } from "../ui/RectTransform";
160
158
  export { RectTransform } from "../ui/RectTransform";
161
159
  export { SpatialHtml } from "../ui/SpatialHtml";
162
160
  export { Text } from "../ui/Text";
161
+ export { GltfExportBox } from "../export/gltf/GltfExport";
162
+ export { GltfExport } from "../export/gltf/GltfExport";
163
+ export { USDZExporter } from "../export/usdz/USDZExporter";
164
+ export { RegisteredAnimationInfo } from "../export/usdz/extensions/Animation";
165
+ export { TransformData } from "../export/usdz/extensions/Animation";
166
+ export { AnimationExtension } from "../export/usdz/extensions/Animation";
@@ -1,12 +1,12 @@
1
- import { Behaviour, GameObject } from "../Component";
1
+ import { Behaviour, GameObject } from "../../Component";
2
2
  import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
3
- import GLTFMeshGPUInstancingExtension from '../../include/three/EXT_mesh_gpu_instancing_exporter.js';
4
- import { Renderer } from "../Renderer";
3
+ import GLTFMeshGPUInstancingExtension from '../../../include/three/EXT_mesh_gpu_instancing_exporter.js';
4
+ import { Renderer } from "../../Renderer";
5
5
  import { Object3D, Vector3 } from "three";
6
- import { SerializationContext } from "../../engine/engine_serialization_core";
7
- import { NEEDLE_components } from "../../engine/extensions/NEEDLE_components";
8
- import { getWorldPosition } from "../../engine/engine_three_utils";
9
- import { BoxHelperComponent } from "../BoxHelperComponent";
6
+ import { SerializationContext } from "../../../engine/engine_serialization_core";
7
+ import { NEEDLE_components } from "../../../engine/extensions/NEEDLE_components";
8
+ import { getWorldPosition } from "../../../engine/engine_three_utils";
9
+ import { BoxHelperComponent } from "../../BoxHelperComponent";
10
10
  import { AnimationClip } from "three";
11
11
 
12
12
 
@@ -0,0 +1,12 @@
1
+ import { USDZObject } from "./types";
2
+
3
+
4
+ export interface IUSDZExporterExtension {
5
+
6
+ get extensionName(): string;
7
+ onBeforeBuildDocument?(context);
8
+ onAfterBuildDocument?(context);
9
+ onExportObject?(object, model : USDZObject, context);
10
+ onAfterSerialize?(context);
11
+ onAfterHierarchy?(context);
12
+ }
@@ -0,0 +1,216 @@
1
+ import { delay, getParam, isiOS, isMobileDevice, isSafari } from "../../../engine/engine_utils";
2
+ import { Object3D, Color } from "three";
3
+ import * as THREE from "three";
4
+ import { USDZExporter as ThreeUSDZExporter } from "three/examples/jsm/exporters/USDZExporter";
5
+ import { AnimationExtension } from "./extensions/Animation"
6
+ import { ensureQuicklookLinkIsCreated } from "./utils/quicklook";
7
+ import { getFormattedDate } from "./utils/timeutils";
8
+ import { registerAnimatorsImplictly } from "./utils/animationutils";
9
+ import { IUSDZExporterExtension } from "./Extension";
10
+ import { Behaviour, GameObject } from "../../Component";
11
+ import { WebXR } from "../../WebXR"
12
+ import { serializable } from "../../../engine/engine_serialization";
13
+ import { showBalloonWarning } from "../../../engine/debug/debug";
14
+ import { Context } from "../../../engine/engine_setup";
15
+
16
+ const debug = getParam("debugusdz");
17
+
18
+ export type QuickLookOverlay = {
19
+ callToAction?: string;
20
+ checkoutTitle?: string;
21
+ checkoutSubtitle?: string;
22
+ }
23
+
24
+ export class USDZExporter extends Behaviour {
25
+
26
+ @serializable(Object3D)
27
+ objectToExport?: THREE.Object3D;
28
+
29
+ @serializable()
30
+ autoExportAnimations: boolean = false;
31
+
32
+ extensions: IUSDZExporterExtension[] = [];
33
+
34
+ private link!: HTMLAnchorElement;
35
+ private webxr?: WebXR;
36
+
37
+
38
+ start() {
39
+ if (debug) {
40
+ window.addEventListener("keydown", (evt) => {
41
+ switch (evt.key) {
42
+ case "t":
43
+ this.exportAsync();
44
+ break;
45
+ }
46
+ });
47
+ if (isMobileDevice()) {
48
+ setTimeout(() => {
49
+ this.exportAsync();
50
+ }, 2000)
51
+ }
52
+ }
53
+ document.getElementById("open-in-ar")?.addEventListener("click", (evt) => {
54
+ evt.preventDefault();
55
+ this.exportAsync();
56
+ });
57
+
58
+ if (!this.objectToExport) this.objectToExport = this.gameObject;
59
+ }
60
+
61
+
62
+
63
+ onEnable() {
64
+ const ios = isiOS()
65
+ const safari = isSafari();
66
+ if (debug || (ios && safari)) {
67
+ this.createQuicklookButton();
68
+ this.lastCallback = this.quicklookCallback.bind(this);
69
+ this.link = ensureQuicklookLinkIsCreated(this.context);
70
+ this.link.addEventListener('message', this.lastCallback);
71
+ }
72
+ }
73
+
74
+ onDisable() {
75
+ this.link?.removeEventListener('message', this.lastCallback);
76
+ }
77
+
78
+ async exportAsync() {
79
+ if (!this.objectToExport) return;
80
+
81
+ const exporter = new ThreeUSDZExporter();
82
+ const extensions: any = [...this.extensions]
83
+
84
+ // collect animators and their clips
85
+ const animExt = new AnimationExtension();
86
+ extensions.push(animExt);
87
+
88
+ if (this.autoExportAnimations)
89
+ registerAnimatorsImplictly(this.objectToExport, animExt);
90
+
91
+ const eventArgs = { self: this, exporter: exporter, extensions: extensions };
92
+ this.dispatchEvent(new CustomEvent("before-export", { detail: eventArgs }))
93
+
94
+ let name = "needle";
95
+ if (debug) name += "-" + getFormattedDate();
96
+
97
+ //@ts-ignore
98
+ exporter.debug = debug;
99
+ //@ts-ignore
100
+ const arraybuffer = await exporter.parse(this.objectToExport, extensions);
101
+ const blob = new Blob([arraybuffer], { type: 'application/octet-stream' });
102
+
103
+ // second file: USDA (without assets)
104
+ //@ts-ignore
105
+ // const usda = exporter.lastUsda;
106
+ // const blob2 = new Blob([usda], { type: 'text/plain' });
107
+ // this.link.download = name + ".usda";
108
+ // this.link.href = URL.createObjectURL(blob2);
109
+ // this.link.click();
110
+
111
+ // see https://developer.apple.com/documentation/arkit/adding_an_apple_pay_button_or_a_custom_action_in_ar_quick_look
112
+ const overlay = this.buildQuicklookOverlay();
113
+ const callToAction = overlay.callToAction ? encodeURIComponent(overlay.callToAction) : "";
114
+ const checkoutTitle = overlay.checkoutTitle ? encodeURIComponent(overlay.checkoutTitle) : "";
115
+ const checkoutSubtitle = overlay.checkoutSubtitle ? encodeURIComponent(overlay.checkoutSubtitle) : "";
116
+ this.link.href = URL.createObjectURL(blob) + `#callToAction=${callToAction}&checkoutTitle=${checkoutTitle}&checkoutSubtitle=${checkoutSubtitle}&`;
117
+
118
+
119
+ if (!this.lastCallback) {
120
+ this.lastCallback = this.quicklookCallback.bind(this);
121
+ this.link.addEventListener('message', this.lastCallback);
122
+ }
123
+
124
+ // open quicklook
125
+ this.link.download = name + ".usdz";
126
+ this.link.click();
127
+
128
+ // TODO detect QuickLook availability:
129
+ // https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/#:~:text=inside%20the%20anchor.-,Feature%20Detection,-To%20detect%20support
130
+ }
131
+
132
+ private lastCallback?: any;
133
+
134
+ private quicklookCallback(event) {
135
+ if ((event as any)?.data == '_apple_ar_quicklook_button_tapped') {
136
+ if (debug) showBalloonWarning("Quicklook closed via call to action button");
137
+ this.dispatchEvent(new CustomEvent("quicklook-button-tapped", { detail: this }));
138
+ }
139
+ }
140
+
141
+ private buildQuicklookOverlay(): QuickLookOverlay {
142
+ const obj: QuickLookOverlay = {};
143
+ obj.callToAction = "Close";
144
+ obj.checkoutTitle = "🌵 Made with Needle";
145
+ obj.checkoutSubtitle = "_";
146
+ // Use the quicklook-overlay event to customize the overlay
147
+ this.dispatchEvent(new CustomEvent("quicklook-overlay", { detail: obj }));
148
+ return obj;
149
+ }
150
+
151
+ private _arButton?: HTMLElement;
152
+ private async createQuicklookButton() {
153
+ if (!this.webxr) {
154
+ await delay(1);
155
+ this.webxr = GameObject.findObjectOfType(WebXR) ?? undefined;
156
+ if (this.webxr) {
157
+ if(this.webxr.VRButton) this.webxr.VRButton.parentElement?.removeChild(this.webxr.VRButton);
158
+ // check if we have an AR button already and re-use that
159
+ if (this.webxr.ARButton && this._arButton !== this.webxr.ARButton) {
160
+ this._arButton = this.webxr.ARButton;
161
+ // Hack to remove the immersiveweb link
162
+ const linkInButton = this.webxr.ARButton.parentElement?.querySelector("a");
163
+ if (linkInButton) {
164
+ linkInButton.href = "";
165
+ }
166
+ this.webxr.ARButton.innerText = "Open in Quicklook";
167
+ this.webxr.ARButton.disabled = false;
168
+ this.webxr.ARButton.addEventListener("click", evt => {
169
+ evt.preventDefault();
170
+ this.exportAsync();
171
+ });
172
+ this.webxr.ARButton.classList.add("quicklook-ar-button");
173
+ }
174
+ // create a button if WebXR didnt create one yet
175
+ else {
176
+ this.webxr.createARButton = false;
177
+ this.webxr.createVRButton = false;
178
+ let container = window.document.querySelector(".webxr-buttons");
179
+ if (!container) {
180
+ container = document.createElement("div");
181
+ container.classList.add("webxr-buttons");
182
+ }
183
+ const button = document.createElement("button");
184
+ button.innerText = "Open in Quicklook";
185
+ button.addEventListener("click", () => {
186
+ this.exportAsync();
187
+ });
188
+ button.classList.add('webxr-ar-button');
189
+ button.classList.add('webxr-button');
190
+ button.classList.add("quicklook-ar-button");
191
+ container.appendChild(button);
192
+ }
193
+ }
194
+ else {
195
+ console.warn("Could not find WebXR component: will not create Quicklook button", Context.Current);
196
+ }
197
+ }
198
+ }
199
+
200
+ private resetStyles(el: HTMLElement) {
201
+ el.style.position = "";
202
+ el.style.top = "";
203
+ el.style.left = "";
204
+ el.style.width = "";
205
+ el.style.height = "";
206
+ el.style.margin = "";
207
+ el.style.padding = "";
208
+ el.style.border = "";
209
+ el.style.background = "";
210
+ el.style.color = "";
211
+ el.style.font = "";
212
+ el.style.textAlign = "";
213
+ el.style.opacity = "";
214
+ el.style.zIndex = "";
215
+ }
216
+ }