@needle-tools/engine 2.51.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 (128) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/needle-engine.d.ts +243 -56
  3. package/dist/needle-engine.js +380 -381
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +41 -42
  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/engine_utils.d.ts +2 -0
  24. package/lib/engine/engine_utils.js +9 -0
  25. package/lib/engine/engine_utils.js.map +1 -1
  26. package/lib/engine/extensions/{NEEDLE_deferred_texture.d.ts → NEEDLE_progressive.d.ts} +2 -2
  27. package/lib/engine/extensions/{NEEDLE_deferred_texture.js → NEEDLE_progressive.js} +10 -9
  28. package/lib/engine/extensions/NEEDLE_progressive.js.map +1 -0
  29. package/lib/engine/extensions/extensions.js +2 -2
  30. package/lib/engine/extensions/extensions.js.map +1 -1
  31. package/lib/engine-components/Camera.d.ts +8 -2
  32. package/lib/engine-components/Camera.js +56 -4
  33. package/lib/engine-components/Camera.js.map +1 -1
  34. package/lib/engine-components/EventTrigger.d.ts +10 -1
  35. package/lib/engine-components/EventTrigger.js +47 -0
  36. package/lib/engine-components/EventTrigger.js.map +1 -1
  37. package/lib/engine-components/Light.js +6 -2
  38. package/lib/engine-components/Light.js.map +1 -1
  39. package/lib/engine-components/OrbitControls.js +6 -2
  40. package/lib/engine-components/OrbitControls.js.map +1 -1
  41. package/lib/engine-components/ParticleSystemModules.js +0 -1
  42. package/lib/engine-components/ParticleSystemModules.js.map +1 -1
  43. package/lib/engine-components/Renderer.d.ts +1 -0
  44. package/lib/engine-components/Renderer.js +13 -6
  45. package/lib/engine-components/Renderer.js.map +1 -1
  46. package/lib/engine-components/ShadowCatcher.js +5 -6
  47. package/lib/engine-components/ShadowCatcher.js.map +1 -1
  48. package/lib/engine-components/Skybox.d.ts +1 -0
  49. package/lib/engine-components/Skybox.js +5 -0
  50. package/lib/engine-components/Skybox.js.map +1 -1
  51. package/lib/engine-components/SpriteRenderer.d.ts +19 -3
  52. package/lib/engine-components/SpriteRenderer.js +154 -41
  53. package/lib/engine-components/SpriteRenderer.js.map +1 -1
  54. package/lib/engine-components/Voip.js +13 -4
  55. package/lib/engine-components/Voip.js.map +1 -1
  56. package/lib/engine-components/WebXRRig.js +12 -0
  57. package/lib/engine-components/WebXRRig.js.map +1 -1
  58. package/lib/engine-components/codegen/components.d.ts +7 -2
  59. package/lib/engine-components/codegen/components.js +7 -2
  60. package/lib/engine-components/codegen/components.js.map +1 -1
  61. package/lib/engine-components/export/{GltfExport.d.ts → gltf/GltfExport.d.ts} +2 -2
  62. package/lib/engine-components/export/{GltfExport.js → gltf/GltfExport.js} +7 -7
  63. package/lib/engine-components/export/gltf/GltfExport.js.map +1 -0
  64. package/lib/engine-components/export/usdz/Extension.d.ts +9 -0
  65. package/lib/engine-components/export/usdz/Extension.js +2 -0
  66. package/lib/engine-components/export/usdz/Extension.js.map +1 -0
  67. package/lib/engine-components/export/usdz/USDZExporter.d.ts +25 -0
  68. package/lib/engine-components/export/usdz/USDZExporter.js +193 -0
  69. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -0
  70. package/lib/engine-components/export/usdz/extensions/Animation.d.ts +44 -0
  71. package/lib/engine-components/export/usdz/extensions/Animation.js +264 -0
  72. package/lib/engine-components/export/usdz/extensions/Animation.js.map +1 -0
  73. package/lib/engine-components/export/usdz/types.d.ts +34 -0
  74. package/lib/engine-components/export/usdz/types.js +2 -0
  75. package/lib/engine-components/export/usdz/types.js.map +1 -0
  76. package/lib/engine-components/export/usdz/utils/animationutils.d.ts +3 -0
  77. package/lib/engine-components/export/usdz/utils/animationutils.js +46 -0
  78. package/lib/engine-components/export/usdz/utils/animationutils.js.map +1 -0
  79. package/lib/engine-components/export/usdz/utils/quicklook.d.ts +2 -0
  80. package/lib/engine-components/export/usdz/utils/quicklook.js +36 -0
  81. package/lib/engine-components/export/usdz/utils/quicklook.js.map +1 -0
  82. package/lib/engine-components/export/usdz/utils/timeutils.d.ts +1 -0
  83. package/lib/engine-components/export/usdz/utils/timeutils.js +15 -0
  84. package/lib/engine-components/export/usdz/utils/timeutils.js.map +1 -0
  85. package/lib/engine-components/timeline/PlayableDirector.js +6 -0
  86. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  87. package/lib/engine-components/ui/Graphic.d.ts +2 -0
  88. package/lib/engine-components/ui/Graphic.js +15 -0
  89. package/lib/engine-components/ui/Graphic.js.map +1 -1
  90. package/lib/engine-components/ui/Utils.d.ts +2 -1
  91. package/lib/engine-components/ui/Utils.js +5 -3
  92. package/lib/engine-components/ui/Utils.js.map +1 -1
  93. package/package.json +2 -2
  94. package/src/engine/codegen/register_types.js +16 -6
  95. package/src/engine/engine_components.ts +2 -2
  96. package/src/engine/engine_networking_instantiate.ts +4 -1
  97. package/src/engine/engine_serialization_builtin_serializer.ts +35 -7
  98. package/src/engine/engine_serialization_core.ts +7 -1
  99. package/src/engine/engine_setup.ts +2 -2
  100. package/src/engine/engine_texture.ts +6 -0
  101. package/src/engine/engine_utils.ts +13 -2
  102. package/src/engine/extensions/{NEEDLE_deferred_texture.ts → NEEDLE_progressive.ts} +18 -17
  103. package/src/engine/extensions/extensions.ts +2 -2
  104. package/src/engine-components/Camera.ts +53 -5
  105. package/src/engine-components/EventTrigger.ts +39 -19
  106. package/src/engine-components/Light.ts +6 -2
  107. package/src/engine-components/OrbitControls.ts +7 -3
  108. package/src/engine-components/ParticleSystemModules.ts +0 -3
  109. package/src/engine-components/Renderer.ts +13 -7
  110. package/src/engine-components/ShadowCatcher.ts +5 -6
  111. package/src/engine-components/Skybox.ts +4 -0
  112. package/src/engine-components/SpriteRenderer.ts +140 -44
  113. package/src/engine-components/Voip.ts +14 -4
  114. package/src/engine-components/WebXRRig.ts +14 -1
  115. package/src/engine-components/codegen/components.ts +7 -2
  116. package/src/engine-components/export/{GltfExport.ts → gltf/GltfExport.ts} +7 -7
  117. package/src/engine-components/export/usdz/Extension.ts +12 -0
  118. package/src/engine-components/export/usdz/USDZExporter.ts +216 -0
  119. package/src/engine-components/export/usdz/extensions/Animation.ts +306 -0
  120. package/src/engine-components/export/usdz/types.ts +39 -0
  121. package/src/engine-components/export/usdz/utils/animationutils.ts +60 -0
  122. package/src/engine-components/export/usdz/utils/quicklook.ts +43 -0
  123. package/src/engine-components/export/usdz/utils/timeutils.ts +20 -0
  124. package/src/engine-components/timeline/PlayableDirector.ts +5 -0
  125. package/src/engine-components/ui/Graphic.ts +15 -1
  126. package/src/engine-components/ui/Utils.ts +5 -3
  127. package/lib/engine/extensions/NEEDLE_deferred_texture.js.map +0 -1
  128. package/lib/engine-components/export/GltfExport.js.map +0 -1
@@ -1,9 +1,48 @@
1
1
  import { Behaviour } from "./Component";
2
2
  import * as THREE from "three";
3
- import { serializable } from "../engine/engine_serialization_decorator";
4
- import { Color, Material, Texture } from "three";
3
+ import { serializable, serializeable } from "../engine/engine_serialization_decorator";
4
+ import { Color, Material, NearestFilter, Texture, Vector2, TextureFilter } from "three";
5
5
  import { RGBAColor } from "./js-extensions/RGBAColor";
6
+ import { getParam } from "../engine/engine_utils";
6
7
 
8
+ const debug = getParam("debugspriterenderer")
9
+
10
+ class SpriteUtils {
11
+
12
+ static cache: { [key: string]: THREE.BufferGeometry } = {};
13
+
14
+ static getOrCreateGeometry(sprite: Sprite): THREE.BufferGeometry {
15
+ if (sprite._geometry) return sprite._geometry;
16
+ if (sprite.guid) {
17
+ if (SpriteUtils.cache[sprite.guid]) {
18
+ if (debug) console.log("Take cached geometry for sprite", sprite.guid);
19
+ return SpriteUtils.cache[sprite.guid];
20
+ }
21
+ }
22
+ const geo = new THREE.BufferGeometry();
23
+ sprite._geometry = geo;
24
+ const vertices = new Float32Array(sprite.triangles.length * 3);
25
+ const uvs = new Float32Array(sprite.triangles.length * 2);
26
+ for (let i = 0; i < sprite.triangles.length; i += 1) {
27
+ const index = sprite.triangles[i];
28
+
29
+ vertices[i * 3] = -sprite.vertices[index].x;
30
+ vertices[i * 3 + 1] = sprite.vertices[index].y;
31
+
32
+ vertices[i * 3 + 2] = 0;
33
+ const uv = sprite.uv[index];
34
+ uvs[i * 2] = uv.x;
35
+ uvs[i * 2 + 1] = 1 - uv.y;
36
+ }
37
+ geo.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
38
+ geo.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
39
+ if (sprite.guid)
40
+ this.cache[sprite.guid] = geo;
41
+ if (debug)
42
+ console.log("Built sprite geometry", sprite, geo);
43
+ return geo;
44
+ }
45
+ }
7
46
 
8
47
  /// <summary>
9
48
  /// <para>SpriteRenderer draw mode.</para>
@@ -30,42 +69,59 @@ class Vec2 {
30
69
 
31
70
  export class Sprite {
32
71
 
72
+ @serializable()
73
+ guid?: string;
33
74
  @serializable(Texture)
34
75
  texture?: THREE.Texture;
35
-
76
+ @serializeable()
36
77
  triangles!: Array<number>;
78
+ @serializeable()
37
79
  uv!: Array<Vec2>;
80
+ @serializeable()
38
81
  vertices!: Array<Vec2>;
39
82
 
40
83
  _geometry?: THREE.BufferGeometry;
41
84
  }
42
85
 
43
- class SpriteUtils {
44
86
 
45
- static getOrCreateGeometry(sprite: Sprite): THREE.BufferGeometry {
46
- if (sprite._geometry) return sprite._geometry;
47
- const geo = new THREE.BufferGeometry();
48
- sprite._geometry = geo;
49
- const vertices = new Float32Array(sprite.triangles.length * 3);
50
- const uvs = new Float32Array(sprite.triangles.length * 2);
51
- for (let i = 0; i < sprite.triangles.length; i += 1) {
52
- const index = sprite.triangles[i];
87
+ class Slice {
88
+ @serializable()
89
+ name!: string;
90
+ @serializable(Vector2)
91
+ offset!: Vector2;
92
+ @serializable(Vector2)
93
+ size!: Vector2;
94
+ }
53
95
 
54
- vertices[i * 3] = -sprite.vertices[index].x;
55
- vertices[i * 3 + 1] = sprite.vertices[index].y;
96
+ const $spriteTexOwner = Symbol("spriteOwner");
56
97
 
57
- vertices[i * 3 + 2] = 0;
58
- const uv = sprite.uv[index];
59
- uvs[i * 2] = uv.x;
60
- uvs[i * 2 + 1] = 1 - uv.y;
61
- }
62
- // console.log(vertices);
63
- geo.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
64
- geo.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
65
- return geo;
98
+ export class SpriteSheet {
99
+
100
+ @serializable(Sprite)
101
+ sprite?: Sprite;
102
+ @serializable()
103
+ index: number = 0;
104
+ @serializable(Slice)
105
+ slices!: Slice[];
106
+
107
+ update() {
108
+ const index = this.index;
109
+ if (index < 0 || index >= this.slices.length)
110
+ return;
111
+ const slice = this.slices[index];
112
+ let tex = this.sprite?.texture;
113
+ if (!tex) return;
114
+ tex.encoding = THREE.sRGBEncoding;
115
+ tex.offset.set(slice.offset.x, slice.offset.y);
116
+ // aniso > 1 makes the texture blurry
117
+ if (tex.minFilter == NearestFilter && tex.magFilter == NearestFilter)
118
+ tex.anisotropy = 1;
119
+ // tex.repeat.set(slice.size.x, -slice.size.y);
120
+ tex.needsUpdate = true;
66
121
  }
67
122
  }
68
123
 
124
+
69
125
  export class SpriteRenderer extends Behaviour {
70
126
 
71
127
  @serializable()
@@ -79,37 +135,69 @@ export class SpriteRenderer extends Behaviour {
79
135
  @serializable(Material)
80
136
  sharedMaterial?: THREE.Material;
81
137
 
82
- get sprite(): Sprite | undefined {
83
- return this._sprite;
138
+ @serializable(SpriteSheet)
139
+ get sprite(): SpriteSheet | undefined {
140
+ return this._spriteSheet;
84
141
  }
85
- set sprite(value: Sprite | undefined) {
86
- if(value === this._sprite) return;
87
- this._sprite = value;
88
- this.updateSprite();
142
+ set sprite(value: SpriteSheet | undefined | number) {
143
+ if (value === this._spriteSheet) return;
144
+ if (typeof value === "number") {
145
+ const index = Math.floor(value);;
146
+ if (index === value)
147
+ this.spriteIndex = index;
148
+ return;
149
+ }
150
+ else {
151
+ this._spriteSheet = value;
152
+ this.updateSprite();
153
+ }
89
154
  }
90
155
 
91
- private _sprite?: Sprite;
156
+ set spriteIndex(value: number) {
157
+ if (!this._spriteSheet) return;
158
+ this._spriteSheet.index = value;
159
+ this._spriteSheet.update();
160
+ }
161
+ get spriteIndex(): number {
162
+ return this._spriteSheet?.index ?? 0;
163
+ }
164
+ get spriteFrames(): number {
165
+ return this._spriteSheet?.slices.length ?? 0;
166
+ }
167
+
168
+ private _spriteSheet?: SpriteSheet;
92
169
  private _currentSprite?: THREE.Mesh;
93
170
 
94
171
  awake(): void {
95
172
  this._currentSprite = undefined;
173
+ if(debug) {
174
+ console.log("Awake", this.name, this, this.sprite?.sprite?.texture);
175
+ if(this.sprite?.sprite?.texture)
176
+ console.log(this.sprite.sprite.texture.minFilter.toString(), this.sprite.sprite.texture.magFilter.toString());
177
+ }
96
178
  }
97
179
 
98
180
  start() {
99
- if (this.drawMode === SpriteDrawMode.Tiled) {
100
- console.warn("Tiled draw mode is not supported yet", this);
101
- }
102
181
  if (!this._currentSprite)
103
182
  this.updateSprite();
104
183
  else if (this.gameObject)
105
184
  this.gameObject.add(this._currentSprite);
106
185
  }
107
186
 
187
+ // frame : number = 0;
188
+ // update(){
189
+ // // const frameRate = 12;
190
+ // // this.frame += frameRate * this.context.time.deltaTime;
191
+ // // if(this.frame >= this.spriteFrames)
192
+ // // this.frame = 0;
193
+ // // this.spriteIndex = Math.floor(this.frame);
194
+ // // console.log(this.spriteIndex);
195
+ // }
196
+
108
197
  private updateSprite() {
109
198
  if (!this.__didAwake) return;
110
- if (!this.sprite) return;
111
- if (!this.sharedMaterial) return;
112
-
199
+ if (!this.sprite?.sprite) return;
200
+ const sprite = this.sprite.sprite;
113
201
  if (!this._currentSprite) {
114
202
  const mat = new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide });
115
203
  if (!mat) return;
@@ -120,17 +208,23 @@ export class SpriteRenderer extends Behaviour {
120
208
  }
121
209
  mat.alphaTest = 0.5;
122
210
 
123
- if (this.sprite.texture && !mat.wireframe) {
124
- const t = this.sprite.texture;
125
- // t.wrapS = THREE.RepeatWrapping;
126
- mat["map"] = t;
211
+ if (sprite.texture && !mat.wireframe) {
212
+ let tex = sprite.texture;
213
+ // the sprite renderer modifies the textue offset
214
+ // so we need to clone the texture
215
+ // if the same texture is used multiple times
216
+ if (tex[$spriteTexOwner] !== undefined && tex[$spriteTexOwner] !== this && this.spriteFrames > 1) {
217
+ tex = sprite!.texture = tex.clone();
218
+ }
219
+ tex[$spriteTexOwner] = this;
220
+ mat["map"] = tex;
127
221
  }
128
-
129
- this._currentSprite = new THREE.Mesh(SpriteUtils.getOrCreateGeometry(this.sprite), mat);
222
+ this.sharedMaterial = mat;
223
+ this._currentSprite = new THREE.Mesh(SpriteUtils.getOrCreateGeometry(sprite), mat);
130
224
  }
131
225
  else {
132
- this._currentSprite.geometry = SpriteUtils.getOrCreateGeometry(this.sprite);
133
- this._currentSprite.material["map"] = this.sprite.texture;
226
+ this._currentSprite.geometry = SpriteUtils.getOrCreateGeometry(sprite);
227
+ this._currentSprite.material["map"] = sprite.texture;
134
228
  }
135
229
 
136
230
  if (this._currentSprite.parent !== this.gameObject) {
@@ -139,5 +233,7 @@ export class SpriteRenderer extends Behaviour {
139
233
  if (this.gameObject)
140
234
  this.gameObject.add(this._currentSprite);
141
235
  }
236
+
237
+ this._spriteSheet?.update();
142
238
  }
143
239
  }
@@ -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
 
@@ -1,9 +1,22 @@
1
- import { Behaviour } from "./Component";
1
+ import { Object3D } from "three";
2
+ import { IGameObject } from "../engine/engine_types";
3
+ import { getParam } from "../engine/engine_utils";
4
+ import { Behaviour, GameObject } from "./Component";
5
+ import { BoxGizmo } from "./Gizmos";
2
6
 
7
+ const debug = getParam("debugrig");
3
8
 
4
9
  export class XRRig extends Behaviour {
5
10
  awake(): void {
6
11
  // const helper = new AxesHelper(.1);
7
12
  // this.gameObject.add(helper);
13
+ if (debug) {
14
+ const gizmoObj = new Object3D() as IGameObject;
15
+ gizmoObj.position.y += .5;
16
+ this.gameObject.add(gizmoObj);
17
+ const gizmo = gizmoObj.addNewComponent(BoxGizmo);
18
+ if (gizmo)
19
+ gizmo.isGizmo = false;
20
+ }
8
21
  }
9
22
  }
@@ -85,6 +85,7 @@ export { SpatialTriggerReceiver } from "../SpatialTrigger";
85
85
  export { SpatialTrigger } from "../SpatialTrigger";
86
86
  export { SpectatorCamera } from "../SpectatorCamera";
87
87
  export { Sprite } from "../SpriteRenderer";
88
+ export { SpriteSheet } from "../SpriteRenderer";
88
89
  export { SpriteRenderer } from "../SpriteRenderer";
89
90
  export { SyncedCamera } from "../SyncedCamera";
90
91
  export { SyncedRoom } from "../SyncedRoom";
@@ -122,8 +123,6 @@ export { Avatar_Brain_LookAt } from "../avatar/Avatar_Brain_LookAt";
122
123
  export { Avatar_MouthShapes } from "../avatar/Avatar_MouthShapes";
123
124
  export { Avatar_MustacheShake } from "../avatar/Avatar_MustacheShake";
124
125
  export { LogStats } from "../debug/LogStats";
125
- export { GltfExportBox } from "../export/GltfExport";
126
- export { GltfExport } from "../export/GltfExport";
127
126
  export { RGBAColor } from "../js-extensions/RGBAColor";
128
127
  export { PlayableDirector } from "../timeline/PlayableDirector";
129
128
  export { SignalAsset } from "../timeline/SignalAsset";
@@ -159,3 +158,9 @@ export { Rect } from "../ui/RectTransform";
159
158
  export { RectTransform } from "../ui/RectTransform";
160
159
  export { SpatialHtml } from "../ui/SpatialHtml";
161
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
+ }