@needle-tools/engine 4.5.0 → 4.5.2

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 (48) hide show
  1. package/dist/{needle-engine.bundle-8e64768b.js → needle-engine.bundle-99bb72c3.js} +7 -7
  2. package/dist/{needle-engine.bundle-4e21a0eb.min.js → needle-engine.bundle-ac883b5a.min.js} +2 -2
  3. package/dist/{needle-engine.bundle-0293e87a.light.js → needle-engine.bundle-adcbea18.light.js} +7 -7
  4. package/dist/{needle-engine.bundle-1679a09a.light.min.js → needle-engine.bundle-b57b2055.light.min.js} +2 -2
  5. package/dist/{needle-engine.bundle-2f187b48.light.umd.cjs → needle-engine.bundle-bcb5a31b.light.umd.cjs} +4 -4
  6. package/dist/{needle-engine.bundle-4366d297.umd.cjs → needle-engine.bundle-caf79082.umd.cjs} +4 -4
  7. package/dist/needle-engine.js +2 -2
  8. package/dist/needle-engine.light.js +2 -2
  9. package/dist/needle-engine.light.min.js +1 -1
  10. package/dist/needle-engine.light.umd.cjs +1 -1
  11. package/dist/needle-engine.min.js +1 -1
  12. package/dist/needle-engine.umd.cjs +1 -1
  13. package/lib/engine/engine_loaders.callbacks.d.ts +1 -0
  14. package/lib/engine/engine_loaders.callbacks.js +1 -0
  15. package/lib/engine/engine_loaders.callbacks.js.map +1 -1
  16. package/lib/engine/engine_utils_format.js +2 -2
  17. package/lib/engine/engine_utils_format.js.map +1 -1
  18. package/package.json +1 -1
  19. package/plugins/vite/build-pipeline.js +22 -6
  20. package/src/engine/engine_loaders.callbacks.ts +1 -0
  21. package/src/engine/engine_utils_format.ts +2 -2
  22. package/lib/engine/engine.d.ts +0 -4
  23. package/lib/engine/engine.js +0 -12
  24. package/lib/engine/engine.js.map +0 -1
  25. package/lib/engine/engine_element.d.ts +0 -113
  26. package/lib/engine/engine_element.js +0 -833
  27. package/lib/engine/engine_element.js.map +0 -1
  28. package/lib/engine/engine_element_attributes.d.ts +0 -72
  29. package/lib/engine/engine_element_attributes.js +0 -2
  30. package/lib/engine/engine_element_attributes.js.map +0 -1
  31. package/lib/engine/engine_element_extras.d.ts +0 -6
  32. package/lib/engine/engine_element_extras.js +0 -14
  33. package/lib/engine/engine_element_extras.js.map +0 -1
  34. package/lib/engine/engine_element_loading.d.ts +0 -44
  35. package/lib/engine/engine_element_loading.js +0 -350
  36. package/lib/engine/engine_element_loading.js.map +0 -1
  37. package/lib/engine/engine_element_overlay.d.ts +0 -21
  38. package/lib/engine/engine_element_overlay.js +0 -167
  39. package/lib/engine/engine_element_overlay.js.map +0 -1
  40. package/lib/engine/engine_scenetools.d.ts +0 -50
  41. package/lib/engine/engine_scenetools.js +0 -322
  42. package/lib/engine/engine_scenetools.js.map +0 -1
  43. package/lib/engine/engine_web_api.d.ts +0 -12
  44. package/lib/engine/engine_web_api.js +0 -113
  45. package/lib/engine/engine_web_api.js.map +0 -1
  46. package/lib/engine-components/FlyControls.d.ts +0 -10
  47. package/lib/engine-components/FlyControls.js +0 -29
  48. package/lib/engine-components/FlyControls.js.map +0 -1
@@ -1,833 +0,0 @@
1
- import { AgXToneMapping, LinearToneMapping, NeutralToneMapping, NoToneMapping } from "three";
2
- import { registerLoader } from "../engine/engine_gltf.js";
3
- import { isDevEnvironment, showBalloonWarning } from "./debug/index.js";
4
- import { PUBLIC_KEY, VERSION } from "./engine_constants.js";
5
- import { calculateProgress01, EngineLoadingView } from "./engine_element_loading.js";
6
- import { arContainerClassName, AROverlayHandler } from "./engine_element_overlay.js";
7
- import { hasCommercialLicense } from "./engine_license.js";
8
- import { setDracoDecoderPath, setDracoDecoderType, setKtx2TranscoderPath } from "./engine_loaders.js";
9
- import { NeedleLoader } from "./engine_scenetools.js";
10
- import { Context } from "./engine_setup.js";
11
- import { getParam } from "./engine_utils.js";
12
- import { RGBAColor } from "./js-extensions/RGBAColor.js";
13
- import { ensureFonts } from "./webcomponents/fonts.js";
14
- //
15
- // registering loader here too to make sure it's imported when using engine via vanilla js
16
- registerLoader(NeedleLoader);
17
- const debug = getParam("debugwebcomponent");
18
- const htmlTagName = "needle-engine";
19
- const vrContainerClassName = "vr";
20
- const desktopContainerClassname = "desktop";
21
- const knownClasses = [arContainerClassName, vrContainerClassName, desktopContainerClassname];
22
- const arSessionActiveClassName = "ar-session-active";
23
- const desktopSessionActiveClassName = "desktop-session-active";
24
- const observedAttributes = [
25
- "public-key",
26
- "version",
27
- "hash",
28
- "src",
29
- "camera-controls",
30
- "loadstart",
31
- "progress",
32
- "loadfinished",
33
- "dracoDecoderPath",
34
- "dracoDecoderType",
35
- "ktx2DecoderPath",
36
- "tone-mapping",
37
- "tone-mapping-exposure",
38
- "background-blurriness",
39
- "background-color",
40
- ];
41
- // https://developers.google.com/web/fundamentals/web-components/customelements
42
- /**
43
- * <needle-engine> web component. See {@link NeedleEngineAttributes} attributes for supported attributes
44
- * The needle engine web component creates and manages a needle engine context which is responsible for rendering a 3D scene using threejs.
45
- * The needle engine context is created when the src attribute is set and disposed when the needle engine is removed from the document (you can prevent this by setting the keep-alive attribute to true).
46
- * The needle engine context is accessible via the context property on the needle engine element (e.g. document.querySelector("needle-engine").context).
47
- * @link https://engine.needle.tools/docs/reference/needle-engine-attributes
48
- *
49
- * @example
50
- * <needle-engine src="https://example.com/scene.glb"></needle-engine>
51
- * @example
52
- * <needle-engine src="https://example.com/scene.glb" camera-controls="false"></needle-engine>
53
- */
54
- export class NeedleEngineHTMLElement extends HTMLElement {
55
- static get observedAttributes() {
56
- return observedAttributes;
57
- }
58
- get loadingProgress01() { return this._loadingProgress01; }
59
- get loadingFinished() { return this.loadingProgress01 > .999; }
60
- /**
61
- * If set to false the camera controls are disabled. Default is true.
62
- * @type {boolean | null}
63
- * @memberof NeedleEngineAttributes
64
- * @example
65
- * <needle-engine camera-controls="false"></needle-engine>
66
- * @example
67
- * <needle-engine camera-controls="true"></needle-engine>
68
- * @example
69
- * <needle-engine camera-controls></needle-engine>
70
- * @example
71
- * <needle-engine></needle-engine>
72
- * @returns {boolean | null} if the attribute is not set it returns null
73
- */
74
- get cameraControls() {
75
- const attr = this.getAttribute("camera-controls");
76
- if (attr == null)
77
- return null;
78
- if (attr === null || attr === "False" || attr === "false" || attr === "0" || attr === "none")
79
- return false;
80
- return true;
81
- }
82
- /**
83
- * Get the current context for this web component instance. The context is created when the src attribute is set and the loading has finished.
84
- * The context is disposed when the needle engine is removed from the document (you can prevent this by setting the keep-alive attribute to true).
85
- * @returns {Promise<Context>} a promise that resolves to the context when the loading has finished
86
- */
87
- getContext() {
88
- return new Promise((res, _rej) => {
89
- if (this._context && this.loadingFinished) {
90
- res(this._context);
91
- }
92
- else {
93
- const cb = () => {
94
- this.removeEventListener("loadfinished", cb);
95
- if (this._context && this.loadingFinished) {
96
- res(this._context);
97
- }
98
- };
99
- this.addEventListener("loadfinished", cb);
100
- }
101
- });
102
- }
103
- /**
104
- * Get the context that is created when the src attribute is set and the loading has finished.
105
- */
106
- get context() { return this._context; }
107
- _context;
108
- _overlay_ar;
109
- _loadingProgress01 = 0;
110
- _loadingView;
111
- _previousSrc = null;
112
- /** set to true after <needle-engine> did load completely at least once. Set to false when <needle-engine> is removed from the document */
113
- _didFullyLoad = false;
114
- constructor() {
115
- super();
116
- this._overlay_ar = new AROverlayHandler();
117
- // TODO: do we want to rename this event?
118
- this.addEventListener("ready", this.onReady);
119
- ensureFonts();
120
- this.attachShadow({ mode: 'open' });
121
- const template = document.createElement('template');
122
- template.innerHTML = `<style>
123
- @import url('https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,100..1000&display=swap');
124
-
125
- :host {
126
- position: absolute;
127
- display: block;
128
- width: max(600px, 100%);
129
- height: max(300px, 100%);
130
- touch-action: none;
131
-
132
- -webkit-tap-highlight-color: transparent;
133
- }
134
-
135
- @media (max-width: 600px) {
136
- :host {
137
- width: 100%;
138
- }
139
- }
140
- @media (max-height: 300px) {
141
- :host {
142
- height: 100%;
143
- }
144
- }
145
-
146
- :host > div.canvas-wrapper {
147
- width: 100%;
148
- height: 100%;
149
- }
150
-
151
- :host canvas {
152
- position: absolute;
153
- user-select: none;
154
- -webkit-user-select: none;
155
-
156
- /** allow touch panning but no pinch zoom **/
157
- /** but this doesnt work yet:
158
- * touch-action: pan-x, pan-y;
159
- **/
160
-
161
- -webkit-touch-callout: none;
162
- -webkit-user-drag: none;
163
- -webkit-user-modify: none;
164
-
165
- left: 0;
166
- top: 0;
167
- }
168
- :host .content {
169
- position: absolute;
170
- top: 0;
171
- width: 100%;
172
- height: 100%;
173
- visibility: visible;
174
- z-index: 500; /* < must be less than the webxr buttons element */
175
- pointer-events: none;
176
- }
177
- :host .overlay-content {
178
- position: absolute;
179
- user-select: auto;
180
- pointer-events: all;
181
- }
182
- :host slot[name="quit-ar"]:hover {
183
- cursor: pointer;
184
- }
185
- :host .quit-ar-button {
186
- position: absolute;
187
- // top: env(titlebar-area-y); /** this doesnt work **/
188
- top: 60px; /** camera access needs a bit more space **/
189
- right: 20px;
190
- z-index: 9999;
191
- }
192
- </style>
193
- <div class="canvas-wrapper"> <!-- this wrapper is necessary for WebXR https://github.com/meta-quest/immersive-web-emulator/issues/55 -->
194
- <canvas></canvas>
195
- </div>
196
- <div class="content">
197
- <slot class="overlay-content"></slot>
198
- </div>
199
- `;
200
- if (this.shadowRoot)
201
- this.shadowRoot.appendChild(template.content.cloneNode(true));
202
- this._context = new Context({ domElement: this });
203
- this.addEventListener("error", this.onError);
204
- }
205
- /**
206
- * @internal
207
- */
208
- async connectedCallback() {
209
- if (debug) {
210
- console.log("<needle-engine> connected");
211
- }
212
- this.setPublicKey();
213
- this.setVersion();
214
- this.addEventListener("xr-session-started", this.onXRSessionStarted);
215
- this.onSetupDesktop();
216
- if (!this.getAttribute("src")) {
217
- const global = globalThis["needle:codegen_files"];
218
- if (debug)
219
- console.log("src is null, trying to load from globalThis[\"needle:codegen_files\"]", global);
220
- if (global) {
221
- if (debug)
222
- console.log("globalThis[\"needle:codegen_files\"]", global);
223
- this.setAttribute("src", global);
224
- }
225
- }
226
- if (debug)
227
- console.log("src", this.getAttribute("src"));
228
- // we have to wait because codegen does set the src attribute when it's loaded
229
- // which might happen after the element is connected
230
- // if the `src` is then still null we want to initialize the default scene
231
- const loadId = this._loadId;
232
- setTimeout(() => {
233
- if (this.isConnected === false)
234
- return;
235
- if (loadId !== this._loadId)
236
- return;
237
- this.onLoad();
238
- }, 1);
239
- }
240
- /**
241
- * @internal
242
- */
243
- disconnectedCallback() {
244
- this.removeEventListener("xr-session-started", this.onXRSessionStarted);
245
- this._didFullyLoad = false;
246
- const keepAlive = this.getAttribute("keep-alive");
247
- const dispose = keepAlive == undefined || (keepAlive?.length > 0 && keepAlive !== "true" && keepAlive !== "1");
248
- if (debug)
249
- console.warn("<needle-engine> disconnected, keep-alive: \"" + keepAlive + "\"", typeof keepAlive, "Dispose=", dispose);
250
- if (dispose) {
251
- if (debug)
252
- console.warn("<needle-engine> dispose");
253
- this._context?.dispose();
254
- this._context = null;
255
- this._lastSourceFiles = null;
256
- this._loadId += 1;
257
- }
258
- else {
259
- if (debug)
260
- console.warn("<needle-engine> is not disposed because keep-alive is set");
261
- }
262
- }
263
- /**
264
- * @internal
265
- */
266
- attributeChangedCallback(name, oldValue, newValue) {
267
- if (debug)
268
- console.log("attributeChangedCallback", name, oldValue, newValue);
269
- switch (name) {
270
- case "src":
271
- if (debug)
272
- console.warn("<needle-engine src>\nchanged from \"", oldValue, "\" to \"", newValue, "\"");
273
- this.onLoad();
274
- // this._watcher?.onSourceChanged(newValue);
275
- break;
276
- case "hash":
277
- if (this._context) {
278
- this._context.hash = newValue;
279
- }
280
- break;
281
- case "loadstart":
282
- case "progress":
283
- case "loadfinished":
284
- if (typeof newValue === "string" && newValue.length > 0) {
285
- if (debug)
286
- console.log(name + " attribute changed", newValue);
287
- this.registerEventFromAttribute(name, newValue);
288
- }
289
- break;
290
- case "dracoDecoderPath":
291
- if (debug)
292
- console.log("dracoDecoderPath", newValue);
293
- setDracoDecoderPath(newValue);
294
- break;
295
- case "dracoDecoderType":
296
- if (newValue === "wasm" || newValue === "js") {
297
- if (debug)
298
- console.log("dracoDecoderType", newValue);
299
- setDracoDecoderType(newValue);
300
- }
301
- else
302
- console.error("Invalid dracoDecoderType", newValue, "expected js or wasm");
303
- break;
304
- case "ktx2DecoderPath":
305
- if (debug)
306
- console.log("ktx2DecoderPath", newValue);
307
- setKtx2TranscoderPath(newValue);
308
- break;
309
- case "tone-mapping": {
310
- this.applyAttributes();
311
- break;
312
- }
313
- case "tone-mapping-exposure": {
314
- this.applyAttributes();
315
- break;
316
- }
317
- case "background-blurriness": {
318
- const value = parseFloat(newValue);
319
- if (value != undefined && this._context) {
320
- this._context.scene.backgroundBlurriness = value;
321
- }
322
- break;
323
- }
324
- case "background-color": {
325
- this.applyAttributes();
326
- break;
327
- }
328
- case "public-key": {
329
- if (newValue != PUBLIC_KEY)
330
- this.setPublicKey();
331
- break;
332
- }
333
- case "version": {
334
- if (newValue != VERSION)
335
- this.setVersion();
336
- break;
337
- }
338
- }
339
- }
340
- _loadId = 0;
341
- _abortController = null;
342
- _lastSourceFiles = null;
343
- _createContextPromise = null;
344
- async onLoad() {
345
- if (!this.isConnected)
346
- return;
347
- if (!this._context) {
348
- if (debug)
349
- console.warn("Create new context");
350
- this._context = new Context({ domElement: this });
351
- }
352
- if (!this._context) {
353
- console.error("Needle Engine: Context not initialized");
354
- return;
355
- }
356
- const filesToLoad = this.getSourceFiles();
357
- if (!this.checkIfSourceHasChanged(filesToLoad, this._lastSourceFiles)) {
358
- return;
359
- }
360
- // Abort previous loading (if it's still running)
361
- if (this._abortController) {
362
- if (debug)
363
- console.warn("Abort previous loading process");
364
- this._abortController.abort();
365
- this._abortController = null;
366
- }
367
- this._lastSourceFiles = filesToLoad;
368
- const loadId = ++this._loadId;
369
- if (filesToLoad === null || filesToLoad === undefined || filesToLoad.length <= 0) {
370
- if (debug)
371
- console.warn("Clear scene", filesToLoad);
372
- this._context.clear();
373
- if (loadId !== this._loadId)
374
- return;
375
- }
376
- const alias = this.getAttribute("alias");
377
- this.classList.add("loading");
378
- // Loading start events
379
- const allowOverridingDefaultLoading = hasCommercialLicense();
380
- // default loading can be overriden by calling preventDefault in the onload start event
381
- this.ensureLoadStartIsRegistered();
382
- let useDefaultLoading = this.dispatchEvent(new CustomEvent("loadstart", {
383
- detail: {
384
- context: this._context,
385
- alias: alias
386
- },
387
- cancelable: true
388
- }));
389
- if (allowOverridingDefaultLoading) {
390
- // Handle the <needle-engine hide-loading-overlay> attribute
391
- const hideOverlay = this.getAttribute("hide-loading-overlay");
392
- if (hideOverlay !== null && hideOverlay !== undefined && hideOverlay !== "0") {
393
- useDefaultLoading = false;
394
- }
395
- }
396
- // for local development we allow overriding the loading screen - but we notify the user that it won't work in a deployed environment
397
- if (useDefaultLoading === false && !allowOverridingDefaultLoading) {
398
- if (!isDevEnvironment())
399
- useDefaultLoading = true;
400
- console.warn("Needle Engine: You need a commercial license to override the default loading view. Visit https://needle.tools/pricing");
401
- if (isDevEnvironment())
402
- showBalloonWarning("You need a <a target=\"_blank\" href=\"https://needle.tools/pricing\">commercial license</a> to override the default loading view. This will not work in production.");
403
- }
404
- // create the loading view idf necessary
405
- if (!this._loadingView && useDefaultLoading)
406
- this._loadingView = new EngineLoadingView(this);
407
- if (useDefaultLoading) {
408
- // Only show the loading screen immedialty if we haven't loaded anything before
409
- if (this._didFullyLoad !== true)
410
- this._loadingView?.onLoadingBegin("begin load");
411
- else {
412
- // If we have loaded a glb previously and are loading a new glb due to e.g. src change
413
- // we don't want to show the loading screen immediately to avoid blinking if the glb to be loaded is tiny
414
- // so we wait a bit and only show the loading screen if the loading takes longer than a short moment
415
- setTimeout(() => {
416
- // If the loading progress is already above ~ 70% we also don't need to show the loading screen anymore
417
- if (this._loadingView && this._loadingProgress01 < 0.3 && this._loadId === loadId)
418
- this._loadingView.onLoadingBegin("begin load");
419
- }, 300);
420
- }
421
- }
422
- if (debug)
423
- console.warn("--------------\nNeedle Engine: Begin loading " + loadId + "\n", filesToLoad);
424
- this.onBeforeBeginLoading();
425
- const loadedFiles = [];
426
- const progressEventDetail = {
427
- context: this._context,
428
- name: "",
429
- progress: {},
430
- index: 0,
431
- count: filesToLoad.length,
432
- totalProgress01: this._loadingProgress01
433
- };
434
- const progressEvent = new CustomEvent("progress", { detail: progressEventDetail });
435
- const displayNames = new Array();
436
- const controller = new AbortController();
437
- this._abortController = controller;
438
- const args = {
439
- files: filesToLoad,
440
- abortSignal: controller.signal,
441
- onLoadingProgress: evt => {
442
- if (debug)
443
- console.debug("Loading progress: ", evt);
444
- if (controller.signal.aborted)
445
- return;
446
- const index = evt.index;
447
- if (!displayNames[index] && evt.name) {
448
- displayNames[index] = getDisplayName(evt.name);
449
- }
450
- evt.name = displayNames[index];
451
- if (useDefaultLoading)
452
- this._loadingView?.onLoadingUpdate(evt);
453
- progressEventDetail.name = evt.name;
454
- progressEventDetail.progress = evt.progress;
455
- this._loadingProgress01 = calculateProgress01(evt);
456
- progressEventDetail.totalProgress01 = this._loadingProgress01;
457
- this.dispatchEvent(progressEvent);
458
- },
459
- onLoadingFinished: (_index, file, glTF) => {
460
- if (debug)
461
- console.debug(`Finished loading \"${file}\" (aborted? ${controller.signal.aborted})`);
462
- if (controller.signal.aborted)
463
- return;
464
- if (glTF) {
465
- loadedFiles.push({
466
- src: file,
467
- file: glTF
468
- });
469
- }
470
- }
471
- };
472
- const currentHash = this.getAttribute("hash");
473
- if (currentHash !== null && currentHash !== undefined)
474
- this._context.hash = currentHash;
475
- this._context.alias = alias;
476
- this._createContextPromise = this._context.create(args);
477
- const success = await this._createContextPromise;
478
- this.applyAttributes();
479
- if (debug)
480
- console.warn("--------------\nNeedle Engine: finished loading " + loadId + "\n", filesToLoad, `Aborted? ${controller.signal.aborted}`);
481
- if (controller.signal.aborted) {
482
- console.log("Loading finished but aborted...");
483
- return;
484
- }
485
- if (this._loadId !== loadId) {
486
- console.log("Load id changed during loading process");
487
- return;
488
- }
489
- this._loadingProgress01 = 1;
490
- if (useDefaultLoading && success) {
491
- this._loadingView?.onLoadingUpdate(1, "creating scene");
492
- }
493
- this._didFullyLoad = true;
494
- this.classList.remove("loading");
495
- this.classList.add("loading-finished");
496
- this.dispatchEvent(new CustomEvent("loadfinished", {
497
- detail: {
498
- context: this._context,
499
- src: alias,
500
- loadedFiles: loadedFiles,
501
- }
502
- }));
503
- }
504
- applyAttributes() {
505
- // set tonemapping if configured
506
- if (this._context?.renderer) {
507
- const attribute = this.getAttribute("tonemapping") || this.getAttribute("tone-mapping");
508
- switch (attribute?.toLowerCase()) {
509
- case "none":
510
- this._context.renderer.toneMapping = NoToneMapping;
511
- break;
512
- case "linear":
513
- this._context.renderer.toneMapping = LinearToneMapping;
514
- break;
515
- case "neutral":
516
- this._context.renderer.toneMapping = NeutralToneMapping;
517
- break;
518
- case "agx":
519
- this._context.renderer.toneMapping = AgXToneMapping;
520
- break;
521
- default:
522
- if (attribute !== null && attribute !== undefined) {
523
- console.warn("Invalid tone-mapping attribute: " + attribute);
524
- }
525
- }
526
- const exposure = this.getAttribute("tone-mapping-exposure");
527
- if (exposure !== null && exposure !== undefined) {
528
- const value = parseFloat(exposure);
529
- if (!isNaN(value))
530
- this._context.renderer.toneMappingExposure = value;
531
- }
532
- }
533
- const backgroundBlurriness = this.getAttribute("background-blurriness");
534
- if (backgroundBlurriness !== null && backgroundBlurriness !== undefined) {
535
- const value = parseFloat(backgroundBlurriness);
536
- if (value !== undefined && this._context) {
537
- this._context.scene.backgroundBlurriness = value;
538
- }
539
- }
540
- const backgroundColor = this.getAttribute("background-color");
541
- if (this._context?.renderer) {
542
- if (typeof backgroundColor === "string" && backgroundColor.length > 0) {
543
- const rgbaColor = RGBAColor.fromColorRepresentation(backgroundColor);
544
- if (debug)
545
- console.debug("<needle-engine> background-color changed, str:", backgroundColor, "→", rgbaColor);
546
- this._context.renderer.setClearColor(rgbaColor, rgbaColor.alpha);
547
- this.context.scene.background = null;
548
- }
549
- }
550
- }
551
- onXRSessionStarted = () => {
552
- const xrSessionMode = this.context.xrSessionMode;
553
- if (xrSessionMode === "immersive-ar")
554
- this.onEnterAR(this.context.xrSession);
555
- else if (xrSessionMode === "immersive-vr")
556
- this.onEnterVR(this.context.xrSession);
557
- // handle session end:
558
- this.context.xrSession?.addEventListener("end", () => {
559
- this.dispatchEvent(new CustomEvent("xr-session-ended", { detail: { session: this.context.xrSession, context: this._context, sessionMode: xrSessionMode } }));
560
- if (xrSessionMode === "immersive-ar")
561
- this.onExitAR(this.context.xrSession);
562
- else if (xrSessionMode === "immersive-vr")
563
- this.onExitVR(this.context.xrSession);
564
- });
565
- };
566
- /** called by the context when the first frame has been rendered */
567
- onReady = () => this._loadingView?.onLoadingFinished();
568
- onError = () => this._loadingView?.setMessage("Loading failed!");
569
- internalSetLoadingMessage(str) {
570
- this._loadingView?.setMessage(str);
571
- }
572
- getSourceFiles() {
573
- const src = this.getAttribute("src");
574
- if (!src)
575
- return [];
576
- let filesToLoad;
577
- // When using globalThis the src is an array already
578
- if (Array.isArray(src)) {
579
- filesToLoad = src;
580
- }
581
- // When assigned from codegen the src is a stringified array
582
- else if (src.startsWith("[") && src.endsWith("]")) {
583
- filesToLoad = JSON.parse(src);
584
- }
585
- // src.toString for an array produces a comma separated list
586
- else if (src.includes(",")) {
587
- filesToLoad = src.split(",");
588
- }
589
- else
590
- filesToLoad = [src];
591
- // filter out invalid or empty strings
592
- for (let i = filesToLoad.length - 1; i >= 0; i--) {
593
- const file = filesToLoad[i];
594
- if (file === "null" || file === "undefined" || file?.length <= 0)
595
- filesToLoad.splice(i, 1);
596
- }
597
- return filesToLoad;
598
- }
599
- checkIfSourceHasChanged(current, previous) {
600
- if (current?.length !== previous?.length)
601
- return true;
602
- if (current == null && previous !== null)
603
- return true;
604
- if (current !== null && previous == null)
605
- return true;
606
- if (current !== null && previous !== null) {
607
- for (let i = 0; i < current?.length; i++) {
608
- if (current[i] !== previous[i])
609
- return true;
610
- }
611
- }
612
- return false;
613
- }
614
- _previouslyRegisteredMap = new Map();
615
- ensureLoadStartIsRegistered() {
616
- const attributeValue = this.getAttribute("loadstart");
617
- if (attributeValue)
618
- this.registerEventFromAttribute("loadstart", attributeValue);
619
- }
620
- registerEventFromAttribute(eventName, code) {
621
- const prev = this._previouslyRegisteredMap.get(eventName);
622
- if (prev) {
623
- this._previouslyRegisteredMap.delete(eventName);
624
- this.removeEventListener(eventName, prev);
625
- }
626
- if (typeof code === "string" && code.length > 0) {
627
- try {
628
- // indirect eval https://esbuild.github.io/content-types/#direct-eval
629
- const fn = (0, eval)(code);
630
- // const fn = new Function(newValue);
631
- if (typeof fn === "function") {
632
- this._previouslyRegisteredMap.set(eventName, fn);
633
- this.addEventListener(eventName, evt => fn?.call(globalThis, this._context, evt));
634
- }
635
- }
636
- catch (err) {
637
- console.error("Error registering event " + eventName + "=\"" + code + "\" failed with the following error:\n", err);
638
- }
639
- }
640
- }
641
- setPublicKey() {
642
- if (PUBLIC_KEY && PUBLIC_KEY.length > 0)
643
- this.setAttribute("public-key", PUBLIC_KEY);
644
- }
645
- setVersion() {
646
- if (VERSION && VERSION.length > 0) {
647
- this.setAttribute("version", VERSION);
648
- }
649
- }
650
- /**
651
- * @internal
652
- */
653
- getAROverlayContainer() {
654
- return this._overlay_ar.createOverlayContainer(this);
655
- }
656
- /**
657
- * @internal
658
- */
659
- getVROverlayContainer() {
660
- for (let i = 0; i < this.children.length; i++) {
661
- const ch = this.children[i];
662
- if (ch.classList.contains("vr"))
663
- return ch;
664
- }
665
- return null;
666
- }
667
- /**
668
- * @internal
669
- */
670
- onEnterAR(session) {
671
- this.onSetupAR();
672
- const overlayContainer = this.getAROverlayContainer();
673
- this._overlay_ar.onBegin(this._context, overlayContainer, session);
674
- this.dispatchEvent(new CustomEvent("enter-ar", { detail: { session: session, context: this._context, htmlContainer: this._overlay_ar?.ARContainer } }));
675
- }
676
- /**
677
- * @internal
678
- */
679
- onExitAR(session) {
680
- this._overlay_ar.onEnd(this._context);
681
- this.onSetupDesktop();
682
- this.dispatchEvent(new CustomEvent("exit-ar", { detail: { session: session, context: this._context, htmlContainer: this._overlay_ar?.ARContainer } }));
683
- }
684
- /**
685
- * @internal
686
- */
687
- onEnterVR(session) {
688
- this.onSetupVR();
689
- this.dispatchEvent(new CustomEvent("enter-vr", { detail: { session: session, context: this._context } }));
690
- }
691
- /**
692
- * @internal
693
- */
694
- onExitVR(session) {
695
- this.onSetupDesktop();
696
- this.dispatchEvent(new CustomEvent("exit-vr", { detail: { session: session, context: this._context } }));
697
- }
698
- onSetupAR() {
699
- this.classList.add(arSessionActiveClassName);
700
- this.classList.remove(desktopSessionActiveClassName);
701
- const arContainer = this.getAROverlayContainer();
702
- if (debug)
703
- console.warn("onSetupAR:", arContainer);
704
- if (arContainer) {
705
- arContainer.classList.add(arSessionActiveClassName);
706
- arContainer.classList.remove(desktopSessionActiveClassName);
707
- }
708
- this.foreachHtmlElement(ch => this.setupElementsForMode(ch, arContainerClassName));
709
- }
710
- onSetupVR() {
711
- this.classList.remove(arSessionActiveClassName);
712
- this.classList.remove(desktopSessionActiveClassName);
713
- this.foreachHtmlElement(ch => this.setupElementsForMode(ch, vrContainerClassName));
714
- }
715
- onSetupDesktop() {
716
- this.classList.remove(arSessionActiveClassName);
717
- this.classList.add(desktopSessionActiveClassName);
718
- const arContainer = this.getAROverlayContainer();
719
- if (arContainer) {
720
- arContainer.classList.remove(arSessionActiveClassName);
721
- arContainer.classList.add(desktopSessionActiveClassName);
722
- }
723
- this.foreachHtmlElement(ch => this.setupElementsForMode(ch, desktopContainerClassname));
724
- }
725
- setupElementsForMode(el, currentSessionType, _session = null) {
726
- if (el === this._context?.renderer?.domElement)
727
- return;
728
- if (el.id === "VRButton" || el.id === "ARButton")
729
- return;
730
- const classList = el.classList;
731
- if (classList.contains(currentSessionType)) {
732
- el.style.visibility = "visible";
733
- if (el.style.display === "none")
734
- el.style.display = "block";
735
- }
736
- else {
737
- // only modify style for elements that have a known class (e.g. marked as vr ar desktop)
738
- for (const known of knownClasses) {
739
- if (el.classList.contains(known)) {
740
- el.style.visibility = "hidden";
741
- el.style.display = "none";
742
- }
743
- }
744
- }
745
- }
746
- foreachHtmlElement(cb) {
747
- for (let i = 0; i < this.children.length; i++) {
748
- const ch = this.children[i];
749
- if (ch.style)
750
- cb(ch);
751
- }
752
- }
753
- onBeforeBeginLoading() {
754
- const customDracoDecoderPath = this.getAttribute("dracoDecoderPath");
755
- if (customDracoDecoderPath) {
756
- if (debug)
757
- console.log("using custom draco decoder path", customDracoDecoderPath);
758
- setDracoDecoderPath(customDracoDecoderPath);
759
- }
760
- const customDracoDecoderType = this.getAttribute("dracoDecoderType");
761
- if (customDracoDecoderType) {
762
- if (debug)
763
- console.log("using custom draco decoder type", customDracoDecoderType);
764
- setDracoDecoderType(customDracoDecoderType);
765
- }
766
- const customKtx2DecoderPath = this.getAttribute("ktx2DecoderPath");
767
- if (customKtx2DecoderPath) {
768
- if (debug)
769
- console.log("using custom ktx2 decoder path", customKtx2DecoderPath);
770
- setKtx2TranscoderPath(customKtx2DecoderPath);
771
- }
772
- }
773
- }
774
- if (typeof window !== "undefined" && !window.customElements.get(htmlTagName))
775
- window.customElements.define(htmlTagName, NeedleEngineHTMLElement);
776
- function getDisplayName(str) {
777
- if (str.startsWith("blob:")) {
778
- return "blob";
779
- }
780
- const parts = str.split("/");
781
- let name = parts[parts.length - 1];
782
- // Remove params
783
- const paramsIndex = name.indexOf("?");
784
- if (paramsIndex > 0)
785
- name = name.substring(0, paramsIndex);
786
- const equalSign = name.indexOf("=");
787
- if (equalSign > 0)
788
- name = name.substring(equalSign);
789
- const extension = name.split(".").pop();
790
- const extensions = ["glb", "gltf", "usdz", "usd", "fbx", "obj", "mtl"];
791
- const matchedIndex = !extension ? -1 : extensions.indexOf(extension.toLowerCase());
792
- if (extension && matchedIndex >= 0) {
793
- name = name.substring(0, name.length - extension.length - 1);
794
- }
795
- name = decodeURIComponent(name);
796
- if (name.length > 3) {
797
- let displayName = "";
798
- let lastCharacterWasSpace = false;
799
- const ignoredCharacters = ["(", ")", "[", "]", "{", "}", ":", ";", ",", ".", "!", "?"];
800
- for (let i = 0; i < name.length; i++) {
801
- let c = name[i];
802
- if (c === "_" || c === "-")
803
- c = " ";
804
- if (c === ' ' && displayName.length <= 0)
805
- continue;
806
- const isIgnored = ignoredCharacters.includes(c);
807
- if (isIgnored)
808
- continue;
809
- const isFirstCharacter = displayName.length === 0;
810
- if (isFirstCharacter) {
811
- c = c.toUpperCase();
812
- }
813
- if (lastCharacterWasSpace && c === " ") {
814
- continue;
815
- }
816
- if (lastCharacterWasSpace) {
817
- c = c.toUpperCase();
818
- }
819
- lastCharacterWasSpace = false;
820
- displayName += c;
821
- if (c === " ") {
822
- lastCharacterWasSpace = true;
823
- }
824
- }
825
- if (isDevEnvironment() && name !== displayName)
826
- console.debug("Generated display name: \"" + name + "\" → \"" + displayName + "\"");
827
- return displayName.trim();
828
- }
829
- if (isDevEnvironment())
830
- console.debug("Loading: use default name", name);
831
- return name;
832
- }
833
- //# sourceMappingURL=engine_element.js.map