@needle-tools/engine 4.12.3 → 4.12.4-next.46bee95
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.
- package/CHANGELOG.md +4 -0
- package/components.needle.json +1 -1
- package/dist/{gltf-progressive-Bfpfaz84.umd.cjs → gltf-progressive-BqUnxvCx.umd.cjs} +1 -1
- package/dist/{gltf-progressive-hFPACYio.min.js → gltf-progressive-CSaX5HQb.min.js} +2 -2
- package/dist/{gltf-progressive-DPunMlEM.js → gltf-progressive-ChnIhDXx.js} +27 -27
- package/dist/{loader.worker-DWzfDpAl.js → loader.worker-C1GG9A7C.js} +6 -6
- package/dist/{needle-engine.bundle-CLPD2ttK.umd.cjs → needle-engine.bundle-CojFvJHR.umd.cjs} +291 -285
- package/dist/{needle-engine.bundle-B3ssYJS0.min.js → needle-engine.bundle-CvasmiEO.min.js} +288 -282
- package/dist/{needle-engine.bundle-u-rSDw6R.js → needle-engine.bundle-DGjkYJDl.js} +7387 -7322
- package/dist/needle-engine.d.ts +53 -28
- package/dist/needle-engine.js +4 -4
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-ClLv0reO.min.js → postprocessing-12-UW7je.min.js} +1 -1
- package/dist/{postprocessing-BHQvwehB.umd.cjs → postprocessing-B3Hu0Ryi.umd.cjs} +1 -1
- package/dist/{postprocessing-DLI2N3LL.js → postprocessing-R535krvT.js} +2 -2
- package/dist/{three-Bf2NBxAw.umd.cjs → three-BzxwLtUE.umd.cjs} +176 -176
- package/dist/{three-BCCkyCA5.js → three-D9pcFbxc.js} +4637 -4636
- package/dist/{three-W7zWTcfP.min.js → three-DMvLgxja.min.js} +176 -176
- package/dist/{three-examples-DB5Uoja4.min.js → three-examples-CIv2roOA.min.js} +1 -1
- package/dist/{three-examples-Djbk6WA4.umd.cjs → three-examples-CjSwCv_b.umd.cjs} +1 -1
- package/dist/{three-examples-D4rE49Ui.js → three-examples-F0MJj0vr.js} +1 -1
- package/dist/{three-mesh-ui-zsOOA5Pq.umd.cjs → three-mesh-ui-BLnJQzMl.umd.cjs} +1 -1
- package/dist/{three-mesh-ui-CIez6qJQ.min.js → three-mesh-ui-BllgajJz.min.js} +1 -1
- package/dist/{three-mesh-ui-3nSSizT4.js → three-mesh-ui-DYyiRn5Y.js} +1 -1
- package/dist/{vendor-tyBvnMF-.umd.cjs → vendor-BFgQSG2m.umd.cjs} +1 -1
- package/dist/{vendor-DMZcbVO1.js → vendor-BIFy-gRe.js} +1 -1
- package/dist/{vendor-sURMCFSI.min.js → vendor-ChgmXMYr.min.js} +1 -1
- package/lib/engine/debug/debug_overlay.js +13 -2
- package/lib/engine/debug/debug_overlay.js.map +1 -1
- package/lib/engine/engine_animation.d.ts +7 -0
- package/lib/engine/engine_animation.js +16 -0
- package/lib/engine/engine_animation.js.map +1 -1
- package/lib/engine/engine_input.js +5 -3
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/webcomponents/WebXRButtons.js +7 -0
- package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
- package/lib/engine/webcomponents/buttons.js +4 -0
- package/lib/engine/webcomponents/buttons.js.map +1 -1
- package/lib/engine/webcomponents/icons.js +44 -5
- package/lib/engine/webcomponents/icons.js.map +1 -1
- package/lib/engine/webcomponents/logo-element.js +0 -1
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +14 -2
- package/lib/engine/webcomponents/needle menu/needle-menu.js +195 -142
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +2 -0
- package/lib/engine/xr/NeedleXRSession.js +19 -10
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine-components/Animation.js +2 -0
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/AnimatorController.js +2 -0
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/Light.d.ts +17 -12
- package/lib/engine-components/Light.js +52 -36
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +9 -2
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +3 -3
- package/plugins/common/license.js +3 -3
- package/src/engine/debug/debug_overlay.ts +15 -2
- package/src/engine/engine_animation.ts +19 -1
- package/src/engine/engine_input.ts +5 -3
- package/src/engine/webcomponents/WebXRButtons.ts +8 -0
- package/src/engine/webcomponents/buttons.ts +5 -0
- package/src/engine/webcomponents/icons.ts +47 -5
- package/src/engine/webcomponents/index.ts +1 -1
- package/src/engine/webcomponents/logo-element.ts +0 -1
- package/src/engine/webcomponents/needle menu/needle-menu.ts +211 -146
- package/src/engine/xr/NeedleXRSession.ts +23 -10
- package/src/engine-components/Animation.ts +4 -1
- package/src/engine-components/AnimatorController.ts +3 -0
- package/src/engine-components/Light.ts +50 -42
- package/src/engine-components/webxr/WebXRImageTracking.ts +12 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Texture } from "three";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
const fontname = "Material Symbols Outlined";
|
|
4
5
|
|
|
5
6
|
/** Returns a HTML element containing an icon. Using https://fonts.google.com/icons
|
|
6
7
|
* As a string you should pass in the name of the icon, e.g. "add" or "delete"
|
|
@@ -14,6 +15,20 @@ export function getIconElement(str: string): HTMLElement {
|
|
|
14
15
|
span.classList.add("material-symbols-outlined", "notranslate");
|
|
15
16
|
span.setAttribute("translate", "no");
|
|
16
17
|
span.innerText = str;
|
|
18
|
+
span.style.visibility = "hidden";
|
|
19
|
+
span.style.userSelect = "none";
|
|
20
|
+
fontReady(fontname).then(res => {
|
|
21
|
+
if (res) span.style.visibility = "";
|
|
22
|
+
else {
|
|
23
|
+
if (str === "more_vert") {
|
|
24
|
+
span.style.visibility = "";
|
|
25
|
+
span.innerText = "More";
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
span.style.display = "none";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
})
|
|
17
32
|
return span;
|
|
18
33
|
}
|
|
19
34
|
|
|
@@ -26,12 +41,8 @@ export function isIconElement(element: Node): boolean {
|
|
|
26
41
|
const textures = new Map<string, Texture | null>();
|
|
27
42
|
|
|
28
43
|
export async function getIconTexture(str: string): Promise<Texture | null> {
|
|
29
|
-
const fontname = "Material Symbols Outlined";
|
|
30
44
|
// check if font has loaded
|
|
31
|
-
|
|
32
|
-
console.log("Font not loaded yet");
|
|
33
|
-
await document.fonts.ready;
|
|
34
|
-
}
|
|
45
|
+
await fontReady(fontname);
|
|
35
46
|
if (textures.has(str)) {
|
|
36
47
|
return textures.get(str) as Texture | null;
|
|
37
48
|
}
|
|
@@ -55,4 +66,35 @@ export async function getIconTexture(str: string): Promise<Texture | null> {
|
|
|
55
66
|
}
|
|
56
67
|
textures.set(str, null);
|
|
57
68
|
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
const loadingPromises: Map<string, Promise<boolean>> = new Map();
|
|
73
|
+
|
|
74
|
+
async function fontReady(fontName: string, retries: number = 5, currentRetry: number = 0): Promise<boolean> {
|
|
75
|
+
const res = document.fonts.check(`1em '${fontName}'`);
|
|
76
|
+
if (!res) {
|
|
77
|
+
await document.fonts.ready;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// check again after waiting for fonts to be ready
|
|
81
|
+
// we cache this promise once but delete it after it has resolved (since we want to retry if it fails) so that we don't have multiple promises for the same font loading at the same time
|
|
82
|
+
const promise = loadingPromises.get(fontName) || document.fonts.load(`1em '${fontName}'`)
|
|
83
|
+
.then(res => res?.length > 0)
|
|
84
|
+
.finally(() => {
|
|
85
|
+
loadingPromises.delete(fontName);
|
|
86
|
+
});
|
|
87
|
+
loadingPromises.set(fontName, promise);
|
|
88
|
+
const loaded = await promise;
|
|
89
|
+
if (!loaded) {
|
|
90
|
+
if (currentRetry < retries) {
|
|
91
|
+
return new Promise(res => {
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
res(fontReady(fontName, retries, currentRetry + 1));
|
|
94
|
+
}, 1000);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
58
100
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
|
|
2
|
-
export { NeedleMenu } from "./needle menu/needle-menu.js";
|
|
2
|
+
export { NeedleMenu } from "./needle menu/needle-menu.js";
|
|
@@ -45,9 +45,17 @@ export declare type ButtonInfo = {
|
|
|
45
45
|
label: string,
|
|
46
46
|
/** Material icon name: https://fonts.google.com/icons */
|
|
47
47
|
icon?: string,
|
|
48
|
-
/**
|
|
48
|
+
/** "left" or "right" to place the icon on the left or right side of the button. Default is "left" */
|
|
49
49
|
iconSide?: "left" | "right",
|
|
50
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Priority controls the order of buttons in the menu.
|
|
52
|
+
* If not enough space is available to show all buttons - the highest priority elements will always be visible
|
|
53
|
+
*
|
|
54
|
+
* **Sorting**
|
|
55
|
+
* Low priority is icon is on the left,
|
|
56
|
+
* high priority is icon is on the right.
|
|
57
|
+
* @default undefined
|
|
58
|
+
*/
|
|
51
59
|
priority?: number;
|
|
52
60
|
/** Experimental. Allows to put two buttons in one row for the compact layout */
|
|
53
61
|
class?: "row2";
|
|
@@ -101,6 +109,20 @@ export declare type ButtonInfo = {
|
|
|
101
109
|
* @category HTML
|
|
102
110
|
*/
|
|
103
111
|
export class NeedleMenu {
|
|
112
|
+
|
|
113
|
+
static setElementPriority(button: HTMLElement, priority: number) {
|
|
114
|
+
button.setAttribute("priority", String(priority));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static getElementPriority(button: HTMLElement): number | undefined {
|
|
118
|
+
const priority = button.getAttribute("priority");
|
|
119
|
+
if (priority) {
|
|
120
|
+
const val = Number.parseFloat(priority);
|
|
121
|
+
if (!Number.isNaN(val)) return val;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
104
126
|
private readonly _context: Context;
|
|
105
127
|
private readonly _menu: NeedleMenuElement;
|
|
106
128
|
private readonly _spatialMenu: NeedleSpatialMenu;
|
|
@@ -274,6 +296,8 @@ export class NeedleMenu {
|
|
|
274
296
|
|
|
275
297
|
}
|
|
276
298
|
|
|
299
|
+
// #region Web component
|
|
300
|
+
|
|
277
301
|
/**
|
|
278
302
|
* `<needle-menu>` web component — lightweight menu used by Needle Engine.
|
|
279
303
|
*
|
|
@@ -333,141 +357,144 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
333
357
|
pointer-events: none;
|
|
334
358
|
}
|
|
335
359
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
width: auto;
|
|
339
|
-
max-width: 95%;
|
|
340
|
-
left: 50%;
|
|
341
|
-
transform: translateX(-50%);
|
|
342
|
-
top: min(20px, 10vh);
|
|
343
|
-
padding: 0.3rem;
|
|
344
|
-
display: flex;
|
|
345
|
-
visibility: visible;
|
|
346
|
-
flex-direction: row-reverse; /* if we overflow this should be right aligned so the logo is always visible */
|
|
347
|
-
pointer-events: all;
|
|
348
|
-
z-index: 1000;
|
|
349
|
-
}
|
|
360
|
+
/** we put base styles in a layer to allow overrides more easily (e.g. the button.mode requested animation should override the base styles) */
|
|
361
|
+
@layer base {
|
|
350
362
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
363
|
+
#root {
|
|
364
|
+
position: absolute;
|
|
365
|
+
width: auto;
|
|
366
|
+
max-width: 95%;
|
|
367
|
+
left: 50%;
|
|
368
|
+
transform: translateX(-50%);
|
|
369
|
+
top: min(20px, 10vh);
|
|
370
|
+
padding: 0.3rem;
|
|
371
|
+
display: flex;
|
|
372
|
+
visibility: visible;
|
|
373
|
+
flex-direction: row-reverse; /* if we overflow this should be right aligned so the logo is always visible */
|
|
374
|
+
pointer-events: all;
|
|
375
|
+
z-index: 1000;
|
|
376
|
+
}
|
|
355
377
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
#root.top {
|
|
362
|
-
top: calc(.7rem + env(safe-area-inset-top));
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
.wrapper {
|
|
366
|
-
position: relative;
|
|
367
|
-
display: flex;
|
|
368
|
-
flex-direction: row;
|
|
369
|
-
justify-content: center;
|
|
370
|
-
align-items: stretch;
|
|
371
|
-
gap: 0px;
|
|
372
|
-
padding: 0 0rem;
|
|
373
|
-
}
|
|
378
|
+
/** hide the menu if it's empty **/
|
|
379
|
+
#root.has-no-options.logo-hidden {
|
|
380
|
+
display: none;
|
|
381
|
+
}
|
|
374
382
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
383
|
+
/** using a div here because then we can change the class for placement **/
|
|
384
|
+
#root.bottom {
|
|
385
|
+
top: auto;
|
|
386
|
+
bottom: min(30px, 10vh);
|
|
387
|
+
}
|
|
388
|
+
#root.top {
|
|
389
|
+
top: calc(.7rem + env(safe-area-inset-top));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.wrapper {
|
|
393
|
+
position: relative;
|
|
394
|
+
display: flex;
|
|
395
|
+
flex-direction: row;
|
|
396
|
+
justify-content: center;
|
|
397
|
+
align-items: stretch;
|
|
398
|
+
gap: 0px;
|
|
399
|
+
padding: 0 0rem;
|
|
400
|
+
}
|
|
385
401
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
top: 0;
|
|
416
|
-
left: 0;
|
|
417
|
-
z-index: -1;
|
|
402
|
+
.wrapper > *, .options > button, .options > select, ::slotted(*) {
|
|
403
|
+
position: relative;
|
|
404
|
+
border: none;
|
|
405
|
+
border-radius: 0;
|
|
406
|
+
outline: 1px solid rgba(0,0,0,0);
|
|
407
|
+
display: flex;
|
|
408
|
+
justify-content: center;
|
|
409
|
+
align-items: center;
|
|
410
|
+
max-height: 2.3rem;
|
|
411
|
+
max-width: 100%;
|
|
412
|
+
|
|
413
|
+
/** basic font settings for all entries **/
|
|
414
|
+
font-size: 1rem;
|
|
415
|
+
font-family: 'Roboto Flex', sans-serif;
|
|
416
|
+
font-optical-sizing: auto;
|
|
417
|
+
font-weight: 400;
|
|
418
|
+
font-variation-settings: "wdth" 100;
|
|
419
|
+
color: rgb(20,20,20);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.options > select[multiple]:hover {
|
|
423
|
+
max-height: 300px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.floating-panel-style {
|
|
427
|
+
background: rgba(255, 255, 255, .4);
|
|
428
|
+
outline: rgb(0 0 0 / 5%) 1px solid;
|
|
429
|
+
border: 1px solid rgba(255, 255, 255, .1);
|
|
430
|
+
box-shadow: 0px 7px 0.5rem 0px rgb(0 0 0 / 6%), inset 0px 0px 1.3rem rgba(0,0,0,.05);
|
|
418
431
|
border-radius: 1.5rem;
|
|
419
|
-
|
|
420
|
-
|
|
432
|
+
/**
|
|
433
|
+
* to make nested background filter work
|
|
434
|
+
* https://stackoverflow.com/questions/60997948/backdrop-filter-not-working-for-nested-elements-in-chrome
|
|
435
|
+
**/
|
|
436
|
+
&::before {
|
|
437
|
+
content: '';
|
|
438
|
+
position: absolute;
|
|
439
|
+
width: 100%;
|
|
440
|
+
height: 100%;
|
|
441
|
+
top: 0;
|
|
442
|
+
left: 0;
|
|
443
|
+
z-index: -1;
|
|
444
|
+
border-radius: 1.5rem;
|
|
445
|
+
-webkit-backdrop-filter: blur(8px);
|
|
446
|
+
backdrop-filter: blur(8px);
|
|
447
|
+
}
|
|
421
448
|
}
|
|
422
|
-
}
|
|
423
449
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
450
|
+
a {
|
|
451
|
+
color: inherit;
|
|
452
|
+
text-decoration: none;
|
|
453
|
+
}
|
|
428
454
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
455
|
+
.options {
|
|
456
|
+
display: flex;
|
|
457
|
+
flex-direction: row;
|
|
458
|
+
align-items: center;
|
|
459
|
+
}
|
|
434
460
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
461
|
+
.options > *, ::slotted(*) {
|
|
462
|
+
max-height: 2.25rem;
|
|
463
|
+
padding: .4rem .5rem;
|
|
464
|
+
}
|
|
439
465
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
+
:host .options > *, ::slotted(*) {
|
|
467
|
+
background: transparent;
|
|
468
|
+
border: none;
|
|
469
|
+
white-space: nowrap;
|
|
470
|
+
transition: all 0.1s linear .02s;
|
|
471
|
+
border-radius: 1.5rem;
|
|
472
|
+
user-select: none;
|
|
473
|
+
}
|
|
474
|
+
:host .options > *:hover, ::slotted(*:hover) {
|
|
475
|
+
cursor: pointer;
|
|
476
|
+
color: black;
|
|
477
|
+
background: rgba(245, 245, 245, .8);
|
|
478
|
+
box-shadow: inset 0 0 1rem rgba(0,0,30,.2);
|
|
479
|
+
outline: rgba(0,0,0,.1) 1px solid;
|
|
480
|
+
}
|
|
481
|
+
:host .options > *:active, ::slotted(*:active) {
|
|
482
|
+
background: rgba(255, 255, 255, .8);
|
|
483
|
+
box-shadow: inset 0px 1px 1px rgba(255,255,255,.5), inset 0 0 2rem rgba(0,0,30,.2), inset 0px 2px 4px rgba(0,0,20,.5);
|
|
484
|
+
transition: all 0.05s linear;
|
|
485
|
+
}
|
|
486
|
+
:host .options > *:focus, ::slotted(*:focus) {
|
|
487
|
+
outline: rgba(255,255,255,.5) 1px solid;
|
|
488
|
+
}
|
|
489
|
+
:host .options > *:focus-visible, ::slotted(*:focus-visible) {
|
|
490
|
+
outline: rgba(0,0,0,.5) 1px solid;
|
|
491
|
+
}
|
|
466
492
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
493
|
+
:host .options > *:disabled, ::slotted(*:disabled) {
|
|
494
|
+
background: rgba(0,0,0,.05);
|
|
495
|
+
color: rgba(60,60,60,.7);
|
|
496
|
+
pointer-events: none;
|
|
497
|
+
}
|
|
471
498
|
}
|
|
472
499
|
|
|
473
500
|
button, ::slotted(button) {
|
|
@@ -475,6 +502,7 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
475
502
|
}
|
|
476
503
|
|
|
477
504
|
/** XR button animation **/
|
|
505
|
+
|
|
478
506
|
:host button.this-mode-is-requested {
|
|
479
507
|
background: repeating-linear-gradient(to right, #fff 0%, #fff 40%, #aaffff 55%, #fff 80%);
|
|
480
508
|
background-size: 200% auto;
|
|
@@ -486,8 +514,12 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
486
514
|
}
|
|
487
515
|
|
|
488
516
|
@keyframes AnimationName {
|
|
489
|
-
0% {
|
|
490
|
-
|
|
517
|
+
0% {
|
|
518
|
+
background-position: 0% 0
|
|
519
|
+
}
|
|
520
|
+
100% {
|
|
521
|
+
background-position: -200% 0
|
|
522
|
+
}
|
|
491
523
|
}
|
|
492
524
|
|
|
493
525
|
|
|
@@ -520,6 +552,10 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
520
552
|
|
|
521
553
|
/** Hide the menu button normally **/
|
|
522
554
|
.compact-menu-button { display: none; }
|
|
555
|
+
|
|
556
|
+
/** Hide the compact only options when not in compact mode */
|
|
557
|
+
.options.compact-only { display: none; }
|
|
558
|
+
|
|
523
559
|
/** And show it when we're in compact mode **/
|
|
524
560
|
.compact .compact-menu-button {
|
|
525
561
|
position: relative;
|
|
@@ -639,12 +675,20 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
639
675
|
padding: .6rem .5rem;
|
|
640
676
|
width: 100%;
|
|
641
677
|
}
|
|
678
|
+
.compact.has-options {
|
|
679
|
+
padding-left: 1rem;
|
|
680
|
+
}
|
|
642
681
|
.compact.has-options .logo {
|
|
643
682
|
border: none;
|
|
644
683
|
padding-left: 0;
|
|
645
|
-
margin-left: 1rem;
|
|
646
684
|
margin-bottom: .02rem;
|
|
647
685
|
}
|
|
686
|
+
.compact .options.compact-only {
|
|
687
|
+
display: initial;
|
|
688
|
+
& > * {
|
|
689
|
+
min-height: 1em;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
648
692
|
.compact .options {
|
|
649
693
|
/** e.g. if we have a very wide menu item like a select with long option names we don't want to overflow **/
|
|
650
694
|
max-width: 100%;
|
|
@@ -671,28 +715,15 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
671
715
|
display: none !important;
|
|
672
716
|
}
|
|
673
717
|
}
|
|
674
|
-
|
|
675
|
-
/* dark mode */
|
|
676
|
-
/*
|
|
677
|
-
@media (prefers-color-scheme: dark) {
|
|
678
|
-
:host {
|
|
679
|
-
background: rgba(0,0,0, .6);
|
|
680
|
-
}
|
|
681
|
-
:host button {
|
|
682
|
-
color: rgba(200,200,200);
|
|
683
|
-
}
|
|
684
|
-
:host button:hover {
|
|
685
|
-
background: rgba(100,100,100, .8);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
*/
|
|
689
718
|
|
|
690
719
|
</style>
|
|
691
720
|
|
|
692
721
|
<div id="root" class="logo-hidden floating-panel-style bottom">
|
|
693
722
|
<div class="wrapper">
|
|
723
|
+
<div class="options compact-only" part="options">
|
|
724
|
+
</div>
|
|
694
725
|
<div class="foldout">
|
|
695
|
-
<div class="options" part="options">
|
|
726
|
+
<div class="options main-container" part="options">
|
|
696
727
|
<slot></slot>
|
|
697
728
|
</div>
|
|
698
729
|
<div class="options" part="options">
|
|
@@ -723,7 +754,8 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
723
754
|
this.root = shadow.querySelector("#root") as HTMLDivElement;
|
|
724
755
|
|
|
725
756
|
this.wrapper = this.root?.querySelector(".wrapper") as HTMLDivElement;
|
|
726
|
-
this.options = this.root?.querySelector(".options") as HTMLDivElement;
|
|
757
|
+
this.options = this.root?.querySelector(".options.main-container") as HTMLDivElement;
|
|
758
|
+
this.optionsCompactMode = this.root?.querySelector(".options.compact-only") as HTMLDivElement;
|
|
727
759
|
this.logoContainer = this.root?.querySelector(".logo") as HTMLDivElement;
|
|
728
760
|
this.compactMenuButton = this.root?.querySelector(".compact-menu-button") as HTMLButtonElement;
|
|
729
761
|
this.compactMenuButton.append(getIconElement("more_vert"));
|
|
@@ -932,6 +964,8 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
932
964
|
private readonly wrapper: HTMLDivElement;
|
|
933
965
|
/** @private contains the buttons and dynamic elements */
|
|
934
966
|
private readonly options: HTMLDivElement;
|
|
967
|
+
/** @private contains options visible when in compact mode */
|
|
968
|
+
private readonly optionsCompactMode: HTMLDivElement;
|
|
935
969
|
/** @private contains the needle-logo html element */
|
|
936
970
|
private readonly logoContainer: HTMLDivElement;
|
|
937
971
|
/** @private compact menu button element */
|
|
@@ -1171,6 +1205,37 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
1171
1205
|
this.foldout.classList.add("floating-panel-style");
|
|
1172
1206
|
}
|
|
1173
1207
|
}
|
|
1208
|
+
|
|
1209
|
+
if (this.root.classList.contains("compact")) {
|
|
1210
|
+
this.optionsCompactMode.childNodes.forEach(element => {
|
|
1211
|
+
element.remove();
|
|
1212
|
+
});
|
|
1213
|
+
// Find items in the folding list with the highest priority
|
|
1214
|
+
// The one with the highest priority will be added to the visible container
|
|
1215
|
+
let priorityItem: HTMLElement | null = null;
|
|
1216
|
+
let priorityValue: number = -10000000;
|
|
1217
|
+
for (let i = 0; i < this.options.children.length; i++) {
|
|
1218
|
+
const element = this.options.children.item(i);
|
|
1219
|
+
if (element instanceof HTMLElement) {
|
|
1220
|
+
const priority = NeedleMenu.getElementPriority(element);
|
|
1221
|
+
if (priority !== undefined && priority > priorityValue) {
|
|
1222
|
+
// check if the element is hidden
|
|
1223
|
+
// @TODO: use computed styles
|
|
1224
|
+
const style = element.style;
|
|
1225
|
+
if (style.display === "none") continue;
|
|
1226
|
+
priorityItem = element;
|
|
1227
|
+
priorityValue = priority;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
if (priorityItem) {
|
|
1232
|
+
const item = priorityItem;
|
|
1233
|
+
const clone = item.cloneNode(true);
|
|
1234
|
+
clone.addEventListener("click", () => item.click())
|
|
1235
|
+
this.optionsCompactMode.appendChild(clone);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1174
1239
|
}, 5) as unknown as number;
|
|
1175
1240
|
|
|
1176
1241
|
const getCurrentWidth = () => {
|
|
@@ -454,6 +454,9 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
454
454
|
*/
|
|
455
455
|
static async start(mode: XRSessionMode | "ar" | "quicklook", init?: XRSessionInit, context?: Context): Promise<NeedleXRSession | null> {
|
|
456
456
|
|
|
457
|
+
// setup session init args, make sure we have default values
|
|
458
|
+
if (!init) init = {};
|
|
459
|
+
|
|
457
460
|
// handle iOS platform where "immersive-ar" is special:
|
|
458
461
|
// - we either launch QuickLook
|
|
459
462
|
// - or forward to the Needle App Clip experience for WebXR AR
|
|
@@ -477,7 +480,9 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
477
480
|
}
|
|
478
481
|
|
|
479
482
|
if (!arSupported && (mode === "immersive-ar" || mode === "ar")) {
|
|
480
|
-
|
|
483
|
+
|
|
484
|
+
this.invokeSessionRequestStart("immersive-ar", init);
|
|
485
|
+
|
|
481
486
|
// Forward to the AppClip experience (Using the apple.com url the appclip overlay shows immediately)
|
|
482
487
|
// const url =`https://appclip.needle.tools/ar?url=${(location.href)}`;
|
|
483
488
|
const url = new URL("https://appclip.apple.com/id?p=tools.needle.launch-app.Clip");
|
|
@@ -511,6 +516,10 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
511
516
|
else window.location.href = urlStr;
|
|
512
517
|
}
|
|
513
518
|
|
|
519
|
+
setTimeout(() => {
|
|
520
|
+
this.invokeSessionRequestEnd("immersive-ar", init || {}, null);
|
|
521
|
+
}, 3000);
|
|
522
|
+
|
|
514
523
|
return null;
|
|
515
524
|
}
|
|
516
525
|
}
|
|
@@ -549,9 +558,6 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
549
558
|
|
|
550
559
|
//performance.mark('NeedleXRSession start');
|
|
551
560
|
|
|
552
|
-
// setup session init args, make sure we have default values
|
|
553
|
-
if (!init) init = {};
|
|
554
|
-
|
|
555
561
|
switch (mode) {
|
|
556
562
|
|
|
557
563
|
// Setup VR initialization parameters
|
|
@@ -615,9 +621,7 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
615
621
|
script.onBeforeXR(mode, init);
|
|
616
622
|
}
|
|
617
623
|
}
|
|
618
|
-
|
|
619
|
-
listener({ mode, init });
|
|
620
|
-
}
|
|
624
|
+
this.invokeSessionRequestStart(mode, init);
|
|
621
625
|
if (debug) showBalloonMessage("Requesting " + mode + " session (" + Date.now() + ")");
|
|
622
626
|
Telemetry.sendEvent(Context.Current, "xr", {
|
|
623
627
|
action: "session_request",
|
|
@@ -637,9 +641,7 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
637
641
|
});
|
|
638
642
|
this._currentSessionRequest = undefined;
|
|
639
643
|
this._currentSessionRequestMode = null;
|
|
640
|
-
|
|
641
|
-
listener({ mode, init, newSession: newSession || null });
|
|
642
|
-
}
|
|
644
|
+
this.invokeSessionRequestEnd(mode, init, newSession);
|
|
643
645
|
if (!newSession) {
|
|
644
646
|
console.warn("XR Session request was rejected");
|
|
645
647
|
return null;
|
|
@@ -650,6 +652,17 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
650
652
|
return session;
|
|
651
653
|
}
|
|
652
654
|
|
|
655
|
+
private static invokeSessionRequestStart(mode: XRSessionMode, init: XRSessionInit) {
|
|
656
|
+
for (const listener of this._sessionRequestStartListeners) {
|
|
657
|
+
listener({ mode, init });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
private static invokeSessionRequestEnd(mode: XRSessionMode, init: XRSessionInit, session: XRSession | null | undefined | void) {
|
|
661
|
+
for (const listener of this._sessionRequestEndListeners) {
|
|
662
|
+
listener({ mode, init, newSession: session || null });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
653
666
|
static setSession(mode: XRSessionMode, session: XRSession, init: XRSessionInit, context: Context) {
|
|
654
667
|
if (this._activeSession) {
|
|
655
668
|
console.error("A XRSession is already running");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AnimationAction, AnimationClip, AnimationMixer, LoopOnce, LoopRepeat } from "three";
|
|
2
|
+
import { AnimationUtils } from "../engine/engine_animation.js";
|
|
2
3
|
|
|
3
4
|
import { Mathf } from "../engine/engine_math.js";
|
|
4
5
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
@@ -447,7 +448,7 @@ export class Animation extends Behaviour implements IAnimationComponent {
|
|
|
447
448
|
action.time = Mathf.lerp(options.minMaxOffsetNormalized.x, options.minMaxOffsetNormalized.y, Math.random()) * clip.duration;
|
|
448
449
|
}
|
|
449
450
|
// If the animation is at the end, reset the time
|
|
450
|
-
else if(action.time >= action.getClip().duration) {
|
|
451
|
+
else if (action.time >= action.getClip().duration) {
|
|
451
452
|
action.time = 0;
|
|
452
453
|
}
|
|
453
454
|
|
|
@@ -473,6 +474,8 @@ export class Animation extends Behaviour implements IAnimationComponent {
|
|
|
473
474
|
action.paused = false;
|
|
474
475
|
action.play();
|
|
475
476
|
|
|
477
|
+
window.requestAnimationFrame(() => AnimationUtils.testIfRootCanAnimate(action));
|
|
478
|
+
|
|
476
479
|
if (debug) console.log("PLAY", action.getClip().name, action)
|
|
477
480
|
const handle = new AnimationHandle(action, this.mixer!, options, _ => {
|
|
478
481
|
this._handles.splice(this._handles.indexOf(handle), 1);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AnimationAction, AnimationClip, AnimationMixer, AxesHelper, Euler, KeyframeTrack, LoopOnce, Object3D, Quaternion, Vector3 } from "three";
|
|
2
2
|
|
|
3
3
|
import { isDevEnvironment } from "../engine/debug/index.js";
|
|
4
|
+
import { AnimationUtils } from "../engine/engine_animation.js";
|
|
4
5
|
import { Mathf } from "../engine/engine_math.js";
|
|
5
6
|
import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
|
|
6
7
|
import { assign, SerializationContext, TypeSerializer } from "../engine/engine_serialization_core.js";
|
|
@@ -775,6 +776,8 @@ export class AnimatorController {
|
|
|
775
776
|
else action.weight = 1;
|
|
776
777
|
action.play();
|
|
777
778
|
|
|
779
|
+
window.requestAnimationFrame(() => AnimationUtils.testIfRootCanAnimate(action));
|
|
780
|
+
|
|
778
781
|
if (this.rootMotionHandler) {
|
|
779
782
|
this.rootMotionHandler.onStart(action);
|
|
780
783
|
}
|