Needle Engine

Changes between version 3.7.0-alpha and 3.6.14
Files changed (11) hide show
  1. src/include/three/ARButton.js +6 -9
  2. src/engine/codegen/register_types.js +2 -2
  3. src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +8 -1
  4. src/engine/debug/debug_console.ts +9 -36
  5. src/engine/debug/debug_overlay.ts +20 -19
  6. src/engine/engine_context.ts +4 -10
  7. src/engine/engine_element_extras.ts +0 -5
  8. src/engine/engine_element_overlay.ts +26 -45
  9. src/engine/engine_element.ts +2 -35
  10. src/engine/engine_input.ts +1 -6
  11. src/engine-components/webxr/WebXR.ts +3 -20
src/include/three/ARButton.js CHANGED
@@ -50,13 +50,11 @@
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
- const overlayElement = options.domOverlay.root;
54
- if (isWebXRViewer)
55
- {
53
+ if (isWebXRViewer) {
56
54
  if(options.domOverlay?.root) {
57
- originalDomOverlayParent = overlayElement.parentNode;
58
- if (originalDomOverlayParent)
59
- {
55
+ const overlayElement = options.domOverlay.root;
56
+ originalDomOverlayParent = overlayElement.parentElement;
57
+ if (originalDomOverlayParent) {
60
58
  console.log("Reparent DOM Overlay to body", overlayElement, overlayElement.style.display);
61
59
  // mozilla webxr does hide elements on session start
62
60
  // this is only necessary if we generated the overlay element
@@ -92,13 +90,12 @@
92
90
 
93
91
  button.textContent = 'START AR';
94
92
 
95
- const overlayElement = options.domOverlay.root;
96
93
  // if we reparented the DOM overlay, we're reverting it here
97
94
  if (originalDomOverlayParent)
98
- originalDomOverlayParent.appendChild(overlayElement);
95
+ originalDomOverlayParent.appendChild(options.domOverlay.root);
99
96
 
100
97
  if (ARButtonControlsDomOverlay)
101
- overlayElement.style.display = 'none';
98
+ options.domOverlay.root.style.display = 'none';
102
99
 
103
100
  currentSession = null;
104
101
 
src/engine/codegen/register_types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components";
5
5
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
@@ -216,7 +216,7 @@
216
216
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
217
217
  import { XRRig } from "../../engine-components/webxr/WebXRRig";
218
218
  import { XRState } from "../../engine-components/XRFlag";
219
-
219
+
220
220
  // Register types
221
221
  TypeStore.add("__Ignore", __Ignore);
222
222
  TypeStore.add("ActionBuilder", ActionBuilder);
src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts CHANGED
@@ -171,10 +171,17 @@
171
171
  }
172
172
 
173
173
  private selfModel!: USDObject;
174
- private targetModels: USDObject[] = [];
174
+ private targetModels!: USDObject[];
175
175
 
176
176
  private static _materialTriggersPerId: { [key: string]: ChangeMaterialOnClick[] } = {}
177
177
 
178
+
179
+ beforeCreateDocument(_ext: BehaviorExtension, _context) {
180
+ this.targetModels = [];
181
+ ChangeMaterialOnClick._materialTriggersPerId = {}
182
+ }
183
+
184
+
178
185
  createBehaviours(_ext: BehaviorExtension, model: USDObject, _context) {
179
186
 
180
187
  const shouldExport = this._objectsWithThisMaterial.find(o => o.gameObject.uuid === model.uuid);
src/engine/debug/debug_console.ts CHANGED
@@ -20,31 +20,19 @@
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)
24
- {
23
+ if (isMobile) {
25
24
  beginWatchingLogs();
26
25
  createConsole(true);
27
- if (isMobile)
28
- {
26
+ if (isMobile) {
29
27
  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)
38
28
  engineElement?.addEventListener("enter-ar", () => {
39
29
  if (showConsole || consoleInstance || getErrorCount() > 0) {
40
30
  if (getParam("noerrors")) return;
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
- // }
31
+ const overlay = engineElement["getAROverlayContainer"]?.call(engineElement);
32
+ const consoleElement = getConsoleElement();
33
+ if (consoleElement && overlay) {
34
+ overlay.appendChild(consoleElement);
35
+ }
48
36
  }
49
37
  });
50
38
  engineElement?.addEventListener("exit-ar", () => {
@@ -115,6 +103,7 @@
115
103
  isLoading = true;
116
104
 
117
105
  const script = document.createElement("script");
106
+ script.src = "https://unpkg.com/vconsole@latest/dist/vconsole.min.js";
118
107
  script.onload = () => {
119
108
  isLoading = false;
120
109
  isVisible = true;
@@ -126,21 +115,6 @@
126
115
  consoleHtmlElement[$defaultConsoleParent] = consoleHtmlElement.parentElement;
127
116
  consoleHtmlElement.style.position = "absolute";
128
117
  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
- // }
144
118
  }
145
119
  consoleInstance.setSwitchPosition(20, 10);
146
120
  consoleSwitchButton = getConsoleSwitchButton();
@@ -189,13 +163,12 @@
189
163
  }
190
164
  }
191
165
  `;
192
- consoleHtmlElement?.prepend(styles);
166
+ document.head.appendChild(styles);
193
167
  if (startHidden === true)
194
168
  hideDebugConsole();
195
169
  }
196
170
 
197
171
  };
198
- script.src = "https://unpkg.com/vconsole@latest/dist/vconsole.min.js";
199
172
  document.body.appendChild(script);
200
173
  }
201
174
 
src/engine/debug/debug_overlay.ts CHANGED
@@ -97,7 +97,7 @@
97
97
  }
98
98
  message = newMessage;
99
99
  }
100
- if (!message || message.length <= 0) return;
100
+ if (message.length <= 0) return;
101
101
  showMessage(type, domElement, message);
102
102
  }
103
103
 
@@ -185,25 +185,26 @@
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");
188
190
  container.classList.add("debug-container");
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);
191
+ container.style.position = "absolute";
192
+ container.style.top = "0";
193
+ container.style.right = "5px";
194
+ container.style.paddingTop = "0px";
195
+ container.style.maxWidth = "70%";
196
+ container.style.maxHeight = "calc(100% - 5px)";
197
+ container.style.zIndex = "1000";
198
+ // container.style.pointerEvents = "none";
199
+ container.style.pointerEvents = "scroll";
200
+ // container.style["-webkit-overflow-scrolling"] = "touch";
201
+ container.style.display = "flex";
202
+ container.style.alignItems = "end";
203
+ container.style.flexDirection = "column";
204
+ container.style.color = "white";
205
+ container.style.overflow = "auto";
206
+ // container.style.border = "1px solid red";
207
+ domElement.appendChild(container);
207
208
 
208
209
  const style = document.createElement("style");
209
210
  style.innerHTML = logsContainerStyles;
src/engine/engine_context.ts CHANGED
@@ -140,12 +140,6 @@
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
-
149
143
  get resolutionScaleFactor() { return this._resolutionScaleFactor; }
150
144
  /** use to scale the resolution up or down of the renderer. default is 1 */
151
145
  set resolutionScaleFactor(val: number) {
@@ -320,7 +314,7 @@
320
314
  this._disposeCallbacks.push(() => this._intersectionObserver?.disconnect());
321
315
  }
322
316
 
323
- private createRenderer() {
317
+ private createRenderer(domElement?: HTMLElement) {
324
318
  this.renderer?.dispose();
325
319
 
326
320
  const params: WebGLRendererParameters = {
@@ -328,7 +322,7 @@
328
322
  };
329
323
 
330
324
  // get canvas already configured in the Needle Engine Web Component
331
- const canvas = this.domElement?.shadowRoot?.querySelector("canvas");
325
+ const canvas = domElement?.shadowRoot?.querySelector("canvas");
332
326
  if (canvas) params.canvas = canvas;
333
327
 
334
328
  this.renderer = new WebGLRenderer(params);
@@ -722,8 +716,8 @@
722
716
  this._sizeChanged = true;
723
717
 
724
718
  if (this._stats) {
725
- this._stats.showPanel(0);
726
- this.domElement.shadowRoot?.appendChild(this._stats.dom);
719
+ this._stats.showPanel(1);
720
+ this.domElement.appendChild(this._stats.dom);
727
721
  }
728
722
 
729
723
  if (debug)
src/engine/engine_element_extras.ts CHANGED
@@ -4,11 +4,6 @@
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
- */
12
7
  export async function registerObservableAttribute(name: string) {
13
8
  const { NeedleEngineHTMLElement } = await import("./engine_element");
14
9
  if (!NeedleEngineHTMLElement.observedAttributes.includes(name))
src/engine/engine_element_overlay.ts CHANGED
@@ -30,7 +30,7 @@
30
30
  this.currentSession = session;
31
31
  this.arContainer = overlayContainer;
32
32
 
33
- const arElements = context.domElement.shadowRoot!.querySelectorAll(`.${arContainerClassName}`);
33
+ const arElements = context.domElement.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.shadowRoot?.prepend(canvas);
79
+ _context.domElement.insertBefore(canvas, _context.domElement.firstChild);
80
80
  }
81
81
 
82
82
  // Fix visibility
@@ -92,45 +92,35 @@
92
92
  }
93
93
 
94
94
  findOrCreateARContainer(element: HTMLElement): HTMLElement {
95
- if(debug) console.log("findOrCreateARContainer");
96
95
  // search in the needle-engine element
97
96
  if (element.classList.contains(arContainerClassName)) {
98
- if(debug) console.log("Found overlay container in needle-engine element");
99
97
  return element;
100
98
  }
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
- };
99
+ if (element.children) {
100
+ for (let i = 0; i < element.children.length; i++) {
101
+ const ch = element.children[i] as HTMLElement;
102
+ if (!ch || !ch.classList) continue;
103
+ if (ch.classList.contains(arContainerClassName)) {
104
+ return ch;
105
+ }
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){
112
- if(debug) console.log("Found overlay container in document");
111
+ if (arElements && arElements.length > 0)
113
112
  return arElements[0] as HTMLElement;
114
- }
115
113
 
116
114
  if (debug)
117
115
  console.log("No overlay container found in document - generating new ony");
118
116
  const el = document.createElement("div");
119
117
  el.classList.add(arContainerClassName);
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;
118
+ el.style.position = "absolute";
119
+ el.style.width = "100%";
120
+ el.style.height = "100%";
121
+ el.style.display = "flex";
122
+ el.style.visibility = "visible";
123
+ return element.appendChild(el);
134
124
  }
135
125
 
136
126
  private onRequestedEndAR() {
@@ -145,33 +135,24 @@
145
135
  }
146
136
 
147
137
  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
-
158
138
  var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
159
- svg.classList.add("quit-ar-button");
160
139
  svg.setAttribute('width', "38px");
161
140
  svg.setAttribute('height', "38px");
162
- quitARSlot.appendChild(svg);
141
+ svg.style.position = 'absolute';
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);
163
149
 
164
150
  var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
165
151
  path.setAttribute('d', 'M 12,12 L 28,28 M 28,12 12,28');
166
152
  path.setAttribute('stroke', '#ddd');
167
153
  path.setAttribute('stroke-width', "3px");
168
154
  svg.appendChild(path);
169
- if(debug) console.log("Created fallback close button", svg, element);
155
+ this._createdAROnlyElements.push(path);
170
156
  }
171
157
 
172
- private appendElement(element: Element, parent: HTMLElement) {
173
- if(parent.shadowRoot) return parent.shadowRoot.appendChild(element);
174
- return parent.appendChild(element);
175
- }
176
-
177
158
  }
src/engine/engine_element.ts CHANGED
@@ -91,41 +91,9 @@
91
91
  constructor() {
92
92
  super();
93
93
  this._overlay_ar = new AROverlayHandler();
94
+ this._context = new Context({ domElement: this });
94
95
  // TODO: do we want to rename this event?
95
96
  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 });
129
97
  this.addEventListener("error", this.onError);
130
98
  }
131
99
 
@@ -169,7 +137,7 @@
169
137
  }
170
138
 
171
139
  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
172
- if (debug) console.log("attributeChangedCallback", name, _oldValue, newValue);
140
+ // console.log(name, oldValue, newValue);
173
141
  switch (name) {
174
142
  case "src":
175
143
  if (debug) console.warn("src changed to type:", typeof newValue, ", from \"", _oldValue, "\" to \"", newValue, "\"")
@@ -445,7 +413,6 @@
445
413
  this.classList.add(arSessionActiveClassName);
446
414
  this.classList.remove(desktopSessionActiveClassName);
447
415
  const arContainer = this.getAROverlayContainer();
448
- if(debug) console.warn("onSetupAR:", arContainer)
449
416
  if (arContainer) {
450
417
  arContainer.classList.add(arSessionActiveClassName);
451
418
  arContainer.classList.remove(desktopSessionActiveClassName);
src/engine/engine_input.ts CHANGED
@@ -397,12 +397,7 @@
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
-
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;
400
+ return evt.target === this.context.renderer.domElement;
406
401
  }
407
402
 
408
403
  private keysPressed: { [key: KeyCode | string]: { pressed: boolean, frame: number, startFrame: number, key: string, code: KeyCode | string } } = {};
src/engine-components/webxr/WebXR.ts CHANGED
@@ -18,7 +18,6 @@
18
18
  import { XRFlag, XRState, XRStateFlag } from "../XRFlag";
19
19
  import { showBalloonWarning } from '../../engine/debug';
20
20
 
21
- const debugWebXR = getParam("debugwebxr");
22
21
 
23
22
  export async function detectARSupport() {
24
23
  if(isMozillaXR()) return true;
@@ -239,26 +238,10 @@
239
238
  let arButton, vrButton;
240
239
  const buttonsContainer = document.createElement('div');
241
240
  buttonsContainer.classList.add("webxr-buttons");
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);
241
+ this.context.domElement.append(buttonsContainer);
256
242
 
257
- const forceButtons = debugWebXR;
258
- if(debugWebXR) console.log("ARSupported?", arSupported, "VRSupported?", vrSupported);
259
-
260
243
  // AR support
261
- if (forceButtons || (this.createARButton && this.enableAR && arSupported))
244
+ if (this.enableAR && this.createARButton && arSupported)
262
245
  {
263
246
  arButton = WebXR.createARButton(this);
264
247
  this._arButton = arButton;
@@ -266,7 +249,7 @@
266
249
  }
267
250
 
268
251
  // VR support
269
- if (forceButtons || (this.createVRButton && this.enableVR && vrSupported)) {
252
+ if (this.createVRButton && this.enableVR && vrSupported) {
270
253
  vrButton = WebXR.createVRButton(this);
271
254
  this._vrButton = vrButton;
272
255
  buttonsContainer.appendChild(vrButton);