@@ -50,11 +50,13 @@
|
|
50
50
|
// Workaround: seems WebXR Viewer has a non-standard behaviour when it comes to DOM Overlay and Canvas;
|
51
51
|
// HTMLElements that are inside the Canvas element are not visible in the DOM Overlay.
|
52
52
|
const isWebXRViewer = /WebXRViewer\//i.test( navigator.userAgent );
|
53
|
-
|
53
|
+
const overlayElement = options.domOverlay.root;
|
54
|
+
if (isWebXRViewer)
|
55
|
+
{
|
54
56
|
if(options.domOverlay?.root) {
|
55
|
-
|
56
|
-
originalDomOverlayParent
|
57
|
-
|
57
|
+
originalDomOverlayParent = overlayElement.parentNode;
|
58
|
+
if (originalDomOverlayParent)
|
59
|
+
{
|
58
60
|
console.log("Reparent DOM Overlay to body", overlayElement, overlayElement.style.display);
|
59
61
|
// mozilla webxr does hide elements on session start
|
60
62
|
// this is only necessary if we generated the overlay element
|
@@ -90,12 +92,13 @@
|
|
90
92
|
|
91
93
|
button.textContent = 'START AR';
|
92
94
|
|
95
|
+
const overlayElement = options.domOverlay.root;
|
93
96
|
// if we reparented the DOM overlay, we're reverting it here
|
94
97
|
if (originalDomOverlayParent)
|
95
|
-
originalDomOverlayParent.appendChild(
|
98
|
+
originalDomOverlayParent.appendChild(overlayElement);
|
96
99
|
|
97
100
|
if (ARButtonControlsDomOverlay)
|
98
|
-
|
101
|
+
overlayElement.style.display = 'none';
|
99
102
|
|
100
103
|
currentSession = null;
|
101
104
|
|
@@ -20,19 +20,31 @@
|
|
20
20
|
if (isLocalNetwork())
|
21
21
|
console.log("Add the ?console query parameter to the url to show the debug console (on mobile it will automatically open for local development when your get errors)");
|
22
22
|
const isMobile = isMobileDevice();
|
23
|
-
if (isMobile)
|
23
|
+
if (isMobile)
|
24
|
+
{
|
24
25
|
beginWatchingLogs();
|
25
26
|
createConsole(true);
|
26
|
-
if (isMobile)
|
27
|
+
if (isMobile)
|
28
|
+
{
|
27
29
|
const engineElement = document.querySelector("needle-engine");
|
30
|
+
// setTimeout(() => {
|
31
|
+
// const el = getConsoleElement();
|
32
|
+
// console.log(el);
|
33
|
+
// if (el) {
|
34
|
+
// const overlay = engineElement["getAROverlayContainer"]?.call(engineElement);
|
35
|
+
// overlay.appendChild(el);
|
36
|
+
// }
|
37
|
+
// }, 1000)
|
28
38
|
engineElement?.addEventListener("enter-ar", () => {
|
29
39
|
if (showConsole || consoleInstance || getErrorCount() > 0) {
|
30
40
|
if (getParam("noerrors")) return;
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
41
|
+
// TODO: this doesnt work with shadow DOM since styles are added to the HEAD by the console script
|
42
|
+
// and therefor not applied anymore when the element is moved to the overlay container
|
43
|
+
// const overlay = engineElement["getAROverlayContainer"]?.call(engineElement);
|
44
|
+
// const consoleElement = getConsoleElement();
|
45
|
+
// if (consoleElement && overlay) {
|
46
|
+
// overlay.appendChild(consoleElement);
|
47
|
+
// }
|
36
48
|
}
|
37
49
|
});
|
38
50
|
engineElement?.addEventListener("exit-ar", () => {
|
@@ -103,7 +115,6 @@
|
|
103
115
|
isLoading = true;
|
104
116
|
|
105
117
|
const script = document.createElement("script");
|
106
|
-
script.src = "https://unpkg.com/vconsole@latest/dist/vconsole.min.js";
|
107
118
|
script.onload = () => {
|
108
119
|
isLoading = false;
|
109
120
|
isVisible = true;
|
@@ -115,6 +126,21 @@
|
|
115
126
|
consoleHtmlElement[$defaultConsoleParent] = consoleHtmlElement.parentElement;
|
116
127
|
consoleHtmlElement.style.position = "absolute";
|
117
128
|
consoleHtmlElement.style.zIndex = Number.MAX_SAFE_INTEGER.toString();
|
129
|
+
// const styleSheetList = document.styleSheets;
|
130
|
+
// for (let i = 0; i < styleSheetList.length; i++) {
|
131
|
+
// const styleSheet = styleSheetList[i];
|
132
|
+
// const firstRule = styleSheet.cssRules[0] as CSSStyleRule;
|
133
|
+
// if(firstRule && firstRule.selectorText === "#__vconsole") {
|
134
|
+
// console.log("found vconsole style sheet");
|
135
|
+
// const styleTag = document.createElement("style");
|
136
|
+
// styleTag.innerHTML = "#__needleconsole {}";
|
137
|
+
// for (let j = 0; j < styleSheet.cssRules.length; j++) {
|
138
|
+
// const rule = styleSheet.cssRules[j] as CSSStyleRule;
|
139
|
+
// styleTag.innerHTML += rule.cssText;
|
140
|
+
// }
|
141
|
+
// consoleHtmlElement.appendChild(styleTag);
|
142
|
+
// }
|
143
|
+
// }
|
118
144
|
}
|
119
145
|
consoleInstance.setSwitchPosition(20, 10);
|
120
146
|
consoleSwitchButton = getConsoleSwitchButton();
|
@@ -163,12 +189,13 @@
|
|
163
189
|
}
|
164
190
|
}
|
165
191
|
`;
|
166
|
-
|
192
|
+
consoleHtmlElement?.prepend(styles);
|
167
193
|
if (startHidden === true)
|
168
194
|
hideDebugConsole();
|
169
195
|
}
|
170
196
|
|
171
197
|
};
|
198
|
+
script.src = "https://unpkg.com/vconsole@latest/dist/vconsole.min.js";
|
172
199
|
document.body.appendChild(script);
|
173
200
|
}
|
174
201
|
|
@@ -97,7 +97,7 @@
|
|
97
97
|
}
|
98
98
|
message = newMessage;
|
99
99
|
}
|
100
|
-
if (message.length <= 0) return;
|
100
|
+
if (!message || message.length <= 0) return;
|
101
101
|
showMessage(type, domElement, message);
|
102
102
|
}
|
103
103
|
|
@@ -185,26 +185,25 @@
|
|
185
185
|
const container = document.createElement("div");
|
186
186
|
errorsMap.set(domElement, container);
|
187
187
|
container.setAttribute("data-needle_engine_debug_overlay", "");
|
188
|
-
container.classList.add(arContainerClassName);
|
189
|
-
container.classList.add("desktop");
|
190
188
|
container.classList.add("debug-container");
|
191
|
-
container.style.
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
189
|
+
container.style.cssText = `
|
190
|
+
position: absolute;
|
191
|
+
top: 0;
|
192
|
+
right: 5px;
|
193
|
+
padding-top: 0px;
|
194
|
+
max-width: 70%;
|
195
|
+
max-height: calc(100% - 5px);
|
196
|
+
z-index: 1000;
|
197
|
+
pointer-events: scroll;
|
198
|
+
display: flex;
|
199
|
+
align-items: end;
|
200
|
+
flex-direction: column;
|
201
|
+
color: white;
|
202
|
+
overflow: auto;
|
203
|
+
`
|
204
|
+
if (domElement.shadowRoot)
|
205
|
+
domElement.shadowRoot.appendChild(container);
|
206
|
+
else domElement.appendChild(container);
|
208
207
|
|
209
208
|
const style = document.createElement("style");
|
210
209
|
style.innerHTML = logsContainerStyles;
|
@@ -140,6 +140,12 @@
|
|
140
140
|
/** the <needle-engine> HTML element */
|
141
141
|
domElement: HTMLElement;
|
142
142
|
|
143
|
+
appendHTMLElement(element: HTMLElement) {
|
144
|
+
if (this.domElement.shadowRoot)
|
145
|
+
return this.domElement.shadowRoot.appendChild(element);
|
146
|
+
else return this.domElement.appendChild(element);
|
147
|
+
}
|
148
|
+
|
143
149
|
get resolutionScaleFactor() { return this._resolutionScaleFactor; }
|
144
150
|
/** use to scale the resolution up or down of the renderer. default is 1 */
|
145
151
|
set resolutionScaleFactor(val: number) {
|
@@ -314,7 +320,7 @@
|
|
314
320
|
this._disposeCallbacks.push(() => this._intersectionObserver?.disconnect());
|
315
321
|
}
|
316
322
|
|
317
|
-
private createRenderer(
|
323
|
+
private createRenderer() {
|
318
324
|
this.renderer?.dispose();
|
319
325
|
|
320
326
|
const params: WebGLRendererParameters = {
|
@@ -322,7 +328,7 @@
|
|
322
328
|
};
|
323
329
|
|
324
330
|
// get canvas already configured in the Needle Engine Web Component
|
325
|
-
const canvas = domElement?.shadowRoot?.querySelector("canvas");
|
331
|
+
const canvas = this.domElement?.shadowRoot?.querySelector("canvas");
|
326
332
|
if (canvas) params.canvas = canvas;
|
327
333
|
|
328
334
|
this.renderer = new WebGLRenderer(params);
|
@@ -716,8 +722,8 @@
|
|
716
722
|
this._sizeChanged = true;
|
717
723
|
|
718
724
|
if (this._stats) {
|
719
|
-
this._stats.showPanel(
|
720
|
-
this.domElement.appendChild(this._stats.dom);
|
725
|
+
this._stats.showPanel(0);
|
726
|
+
this.domElement.shadowRoot?.appendChild(this._stats.dom);
|
721
727
|
}
|
722
728
|
|
723
729
|
if (debug)
|
@@ -4,6 +4,11 @@
|
|
4
4
|
DO NOT IMPORT ENGINE_ELEMENT FROM HERE
|
5
5
|
*/
|
6
6
|
|
7
|
+
/**
|
8
|
+
* Call with the name of an attribute that you want to receive change events for
|
9
|
+
* This is useful for example if you want to add custom attributes to <needle-engine>
|
10
|
+
* Use the addAttributeChangeCallback utility methods to register callback events
|
11
|
+
*/
|
7
12
|
export async function registerObservableAttribute(name: string) {
|
8
13
|
const { NeedleEngineHTMLElement } = await import("./engine_element");
|
9
14
|
if (!NeedleEngineHTMLElement.observedAttributes.includes(name))
|
@@ -30,7 +30,7 @@
|
|
30
30
|
this.currentSession = session;
|
31
31
|
this.arContainer = overlayContainer;
|
32
32
|
|
33
|
-
const arElements = context.domElement.querySelectorAll(`.${arContainerClassName}`);
|
33
|
+
const arElements = context.domElement.shadowRoot!.querySelectorAll(`.${arContainerClassName}`);
|
34
34
|
arElements.forEach(el => {
|
35
35
|
if (!el) return;
|
36
36
|
if (el === this.arContainer) return;
|
@@ -76,7 +76,7 @@
|
|
76
76
|
// Canvas is not in DOM anymore after AR using Mozilla XR
|
77
77
|
const canvas = _context.renderer.domElement;
|
78
78
|
if (canvas) {
|
79
|
-
_context.domElement.
|
79
|
+
_context.domElement.shadowRoot?.prepend(canvas);
|
80
80
|
}
|
81
81
|
|
82
82
|
// Fix visibility
|
@@ -92,35 +92,45 @@
|
|
92
92
|
}
|
93
93
|
|
94
94
|
findOrCreateARContainer(element: HTMLElement): HTMLElement {
|
95
|
+
if(debug) console.log("findOrCreateARContainer");
|
95
96
|
// search in the needle-engine element
|
96
97
|
if (element.classList.contains(arContainerClassName)) {
|
98
|
+
if(debug) console.log("Found overlay container in needle-engine element");
|
97
99
|
return element;
|
98
100
|
}
|
99
|
-
if (element.
|
100
|
-
|
101
|
-
|
102
|
-
if (
|
103
|
-
|
104
|
-
|
105
|
-
}
|
106
|
-
}
|
101
|
+
if (element.shadowRoot) {
|
102
|
+
const el = element.shadowRoot!.querySelector(`.${arContainerClassName}`);
|
103
|
+
if (el) {
|
104
|
+
if(debug) console.log("Found overlay container in needle-engine element");
|
105
|
+
return el as HTMLElement;
|
106
|
+
};
|
107
107
|
}
|
108
108
|
|
109
109
|
// search in document as well; "ar" element could live outside needle-engine element
|
110
110
|
const arElements = document.getElementsByClassName(arContainerClassName);
|
111
|
-
if (arElements && arElements.length > 0)
|
111
|
+
if (arElements && arElements.length > 0){
|
112
|
+
if(debug) console.log("Found overlay container in document");
|
112
113
|
return arElements[0] as HTMLElement;
|
114
|
+
}
|
113
115
|
|
114
116
|
if (debug)
|
115
117
|
console.log("No overlay container found in document - generating new ony");
|
116
118
|
const el = document.createElement("div");
|
117
119
|
el.classList.add(arContainerClassName);
|
118
|
-
el.style.
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
120
|
+
el.style.cssText = `
|
121
|
+
position: fixed;
|
122
|
+
top: 0;
|
123
|
+
left: 0;
|
124
|
+
width: 100%;
|
125
|
+
height: 100%;
|
126
|
+
display: flex;
|
127
|
+
visibility: visible;
|
128
|
+
z-index: 9999;
|
129
|
+
pointer-events: none;
|
130
|
+
// background: rgba(0,0,0,1);
|
131
|
+
`;
|
132
|
+
if(debug) this.createFallbackCloseARButton(element);
|
133
|
+
return this.appendElement(el, element) as HTMLElement;
|
124
134
|
}
|
125
135
|
|
126
136
|
private onRequestedEndAR() {
|
@@ -135,24 +145,33 @@
|
|
135
145
|
}
|
136
146
|
|
137
147
|
private createFallbackCloseARButton(element: HTMLElement) {
|
148
|
+
const quitARSlot = document.createElement("slot");
|
149
|
+
quitARSlot.setAttribute("name", "quit-ar");
|
150
|
+
this.appendElement(quitARSlot, element);
|
151
|
+
if(debug) quitARSlot.addEventListener('click', () => console.log("Clicked fallback close button"));
|
152
|
+
quitARSlot.addEventListener('click', this.closeARCallback);
|
153
|
+
this._createdAROnlyElements.push(quitARSlot);
|
154
|
+
// for mozilla XR reparenting we have to make sure the close button is clickable so we set it on the element directly
|
155
|
+
// it's in general perhaps more safe to set it on the element to ensure it's clickable
|
156
|
+
quitARSlot.style.pointerEvents = "auto";
|
157
|
+
|
138
158
|
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
159
|
+
svg.classList.add("quit-ar-button");
|
139
160
|
svg.setAttribute('width', "38px");
|
140
161
|
svg.setAttribute('height', "38px");
|
141
|
-
svg
|
142
|
-
svg.style.right = '20px';
|
143
|
-
svg.style.top = '40px';
|
144
|
-
svg.style.zIndex = '9999';
|
145
|
-
svg.style.pointerEvents = 'auto';
|
146
|
-
svg.addEventListener('click', this.closeARCallback);
|
147
|
-
element.appendChild(svg);
|
148
|
-
this._createdAROnlyElements.push(svg);
|
162
|
+
quitARSlot.appendChild(svg);
|
149
163
|
|
150
164
|
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
151
165
|
path.setAttribute('d', 'M 12,12 L 28,28 M 28,12 12,28');
|
152
166
|
path.setAttribute('stroke', '#ddd');
|
153
167
|
path.setAttribute('stroke-width', "3px");
|
154
168
|
svg.appendChild(path);
|
155
|
-
|
169
|
+
if(debug) console.log("Created fallback close button", svg, element);
|
156
170
|
}
|
157
171
|
|
172
|
+
private appendElement(element: Element, parent: HTMLElement) {
|
173
|
+
if(parent.shadowRoot) return parent.shadowRoot.appendChild(element);
|
174
|
+
return parent.appendChild(element);
|
175
|
+
}
|
176
|
+
|
158
177
|
}
|
@@ -91,9 +91,41 @@
|
|
91
91
|
constructor() {
|
92
92
|
super();
|
93
93
|
this._overlay_ar = new AROverlayHandler();
|
94
|
-
this._context = new Context({ domElement: this });
|
95
94
|
// TODO: do we want to rename this event?
|
96
95
|
this.addEventListener("ready", this.onReady);
|
96
|
+
|
97
|
+
this.attachShadow({mode: 'open'});
|
98
|
+
const template = document.createElement('template');
|
99
|
+
template.innerHTML =
|
100
|
+
`<style>
|
101
|
+
:host {
|
102
|
+
position: relative;
|
103
|
+
display: block;
|
104
|
+
width: 600px;
|
105
|
+
height: 300px;
|
106
|
+
}
|
107
|
+
:host canvas {
|
108
|
+
position: absolute;
|
109
|
+
user-select: none;
|
110
|
+
touch-action: none;
|
111
|
+
}
|
112
|
+
:host slot[name="quit-ar"]:hover {
|
113
|
+
cursor: pointer;
|
114
|
+
}
|
115
|
+
:host .quit-ar-button {
|
116
|
+
position: absolute;
|
117
|
+
top: 40px;
|
118
|
+
right: 20px;
|
119
|
+
z-index: 9999;
|
120
|
+
}
|
121
|
+
</style>
|
122
|
+
<canvas></canvas>
|
123
|
+
`;
|
124
|
+
|
125
|
+
if (this.shadowRoot)
|
126
|
+
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
127
|
+
|
128
|
+
this._context = new Context({ domElement: this });
|
97
129
|
this.addEventListener("error", this.onError);
|
98
130
|
}
|
99
131
|
|
@@ -137,7 +169,7 @@
|
|
137
169
|
}
|
138
170
|
|
139
171
|
attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
|
140
|
-
|
172
|
+
if (debug) console.log("attributeChangedCallback", name, _oldValue, newValue);
|
141
173
|
switch (name) {
|
142
174
|
case "src":
|
143
175
|
if (debug) console.warn("src changed to type:", typeof newValue, ", from \"", _oldValue, "\" to \"", newValue, "\"")
|
@@ -413,6 +445,7 @@
|
|
413
445
|
this.classList.add(arSessionActiveClassName);
|
414
446
|
this.classList.remove(desktopSessionActiveClassName);
|
415
447
|
const arContainer = this.getAROverlayContainer();
|
448
|
+
if(debug) console.warn("onSetupAR:", arContainer)
|
416
449
|
if (arContainer) {
|
417
450
|
arContainer.classList.add(arSessionActiveClassName);
|
418
451
|
arContainer.classList.remove(desktopSessionActiveClassName);
|
@@ -397,7 +397,12 @@
|
|
397
397
|
// if(evt.target === this.context.renderer.domElement) return true;
|
398
398
|
// const css = window.getComputedStyle(evt.target as HTMLElement);
|
399
399
|
// if(css.pointerEvents === "all") return false;
|
400
|
-
|
400
|
+
|
401
|
+
// We only check the target elements here since the canvas may be overlapped by other elements
|
402
|
+
// in which case we do not want to use the input (e.g. if a HTML element is being triggered)
|
403
|
+
if(evt.target === this.context.renderer.domElement) return true;
|
404
|
+
if(evt.target === this.context.domElement) return true;
|
405
|
+
return false;
|
401
406
|
}
|
402
407
|
|
403
408
|
private keysPressed: { [key: KeyCode | string]: { pressed: boolean, frame: number, startFrame: number, key: string, code: KeyCode | string } } = {};
|
@@ -18,6 +18,7 @@
|
|
18
18
|
import { XRFlag, XRState, XRStateFlag } from "../XRFlag";
|
19
19
|
import { showBalloonWarning } from '../../engine/debug';
|
20
20
|
|
21
|
+
const debugWebXR = getParam("debugwebxr");
|
21
22
|
|
22
23
|
export async function detectARSupport() {
|
23
24
|
if(isMozillaXR()) return true;
|
@@ -238,10 +239,26 @@
|
|
238
239
|
let arButton, vrButton;
|
239
240
|
const buttonsContainer = document.createElement('div');
|
240
241
|
buttonsContainer.classList.add("webxr-buttons");
|
241
|
-
|
242
|
+
buttonsContainer.style.cssText = `
|
243
|
+
position: fixed;
|
244
|
+
bottom: 21px;
|
245
|
+
left: 50%;
|
246
|
+
transform: translate(-50%, 0%);
|
247
|
+
z-index: 1000;
|
248
|
+
|
249
|
+
display: flex;
|
250
|
+
flex-direction: row;
|
251
|
+
justify-content: center;
|
252
|
+
align-items: flex-start;
|
253
|
+
gap: 10px;
|
254
|
+
`;
|
255
|
+
this.context.appendHTMLElement(buttonsContainer);
|
242
256
|
|
257
|
+
const forceButtons = debugWebXR;
|
258
|
+
if(debugWebXR) console.log("ARSupported?", arSupported, "VRSupported?", vrSupported);
|
259
|
+
|
243
260
|
// AR support
|
244
|
-
if (this.
|
261
|
+
if (forceButtons || (this.createARButton && this.enableAR && arSupported))
|
245
262
|
{
|
246
263
|
arButton = WebXR.createARButton(this);
|
247
264
|
this._arButton = arButton;
|
@@ -249,7 +266,7 @@
|
|
249
266
|
}
|
250
267
|
|
251
268
|
// VR support
|
252
|
-
if (this.createVRButton && this.enableVR && vrSupported) {
|
269
|
+
if (forceButtons || (this.createVRButton && this.enableVR && vrSupported)) {
|
253
270
|
vrButton = WebXR.createVRButton(this);
|
254
271
|
this._vrButton = vrButton;
|
255
272
|
buttonsContainer.appendChild(vrButton);
|