@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
167 lines • 7.47 kB
JavaScript
import { DeviceUtilities, getParam } from "../engine_utils.js";
const debug = getParam("debugoverlay");
export const arContainerClassName = "ar";
export const quitARClassName = "quit-ar";
// https://developers.google.com/web/fundamentals/web-components/customelements
/** @internal */
export class AROverlayHandler {
get ARContainer() { return this.arContainer; }
arContainer = null;
currentSession = null;
_createdAROnlyElements = [];
_reparentedObjects = [];
contentElement = null;
originalDomOverlayParent = null;
requestEndAR = () => {
this.onRequestedEndAR();
};
onBegin(context, overlayContainer, session) {
this.currentSession = session;
this.arContainer = overlayContainer;
if (DeviceUtilities.isMozillaXR()) {
const arElements = context.domElement.children;
for (let i = 0; i < arElements?.length; i++) {
const el = arElements[i];
if (!el)
return;
if (el === this.arContainer)
return;
this._reparentedObjects.push({ el: el, previousParent: el.parentElement });
this.arContainer?.appendChild(el);
}
if (overlayContainer) {
this.originalDomOverlayParent = overlayContainer.parentNode;
if (this.originalDomOverlayParent) {
console.log("Reparent DOM Overlay to body", overlayContainer, overlayContainer.style.display);
// mozilla webxr does hide elements on session start
// this is only necessary if we generated the overlay element
overlayContainer.style.display = "";
overlayContainer.style.visibility = "";
document.body.appendChild(overlayContainer);
}
}
else {
console.warn("WebXRViewer: No DOM Overlay found");
}
}
this.ensureQuitARButton(this.arContainer);
}
onEnd(_context) {
// if (this.arContainer)
// this.arContainer.classList.remove("ar-session-active");
for (const created of this._createdAROnlyElements) {
if (created.remove) {
created.remove();
}
}
for (const prev of this._reparentedObjects) {
const el = prev.el;
prev.previousParent?.appendChild(el);
}
this._reparentedObjects.length = 0;
// mozilla XR exit AR fixes
if (DeviceUtilities.isMozillaXR()) {
// without the timeout we get errors in mozillas code and can not enter XR again
// not sure why we have to wait
setTimeout(() => {
// Canvas is not in DOM anymore after AR using Mozilla XR
const canvas = _context.renderer.domElement;
if (canvas) {
_context.domElement.shadowRoot?.prepend(canvas);
}
// Fix visibility
const elements = document.querySelectorAll("*");
for (var i = 0; i < elements.length; i++) {
const child = elements[i];
if (child && child._displayChanged !== undefined && child._displayWas !== undefined) {
child.style.display = child._displayWas;
}
}
}, 10);
}
}
createOverlayContainer(needleEngineElement) {
if (this.contentElement)
return this.contentElement;
if (debug)
console.log("Setup overlay container");
const contentElement = needleEngineElement.shadowRoot.querySelector(".content");
this.contentElement = contentElement;
const overlaySlot = needleEngineElement.shadowRoot.querySelector(".overlay-content");
if (overlaySlot)
contentElement.appendChild(overlaySlot);
if (debug && !DeviceUtilities.isMobileDevice())
this.ensureQuitARButton(contentElement);
return contentElement;
}
onRequestedEndAR() {
if (!this.currentSession)
return;
this.currentSession.end();
this.currentSession = null;
}
ensureQuitARButton(element) {
const quitARSlot = document.createElement("slot");
quitARSlot.setAttribute("name", "quit-ar");
this.appendElement(quitARSlot, element);
this._createdAROnlyElements.push(quitARSlot);
// for mozilla XR reparenting we have to make sure the close button is clickable so we set it on the element directly
// it's in general perhaps more safe to set it on the element to ensure it's clickable
quitARSlot.style.pointerEvents = "auto";
// We want to search the document if there's a quit-ar button
// In which case we don't want to populate the default button (slot) with any content
const quitARElement = document.querySelector(`.${quitARClassName}`);
if (quitARElement) {
quitARElement.addEventListener('click', this.requestEndAR);
if (debug)
quitARElement.addEventListener('click', () => console.log("Clicked quit-ar button"));
// We found a explicit quit-ar element
return;
}
quitARSlot.addEventListener('click', this.requestEndAR);
if (debug)
quitARSlot.addEventListener('click', () => console.log("Clicked fallback close button"));
// we need another container to make sure the button is always on top
const fixedButtonContainer = document.createElement("div");
fixedButtonContainer.style.cssText = `
position: fixed;
top: 0;
right: 0;
z-index: 600;
pointer-events: all;
`;
this.appendElement(fixedButtonContainer, quitARSlot);
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.classList.add("quit-ar-button");
svg.setAttribute('width', "40px");
svg.setAttribute('height', "40px");
svg.style.cssText = `
background: rgba(255, 255, 255, .4);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
border-radius: 50%;
box-shadow: 0 0 5px rgba(0,0,0,.3);
outline: 1px solid rgba(255, 255, 255, .6);
display: flex;
justify-content: center;
align-items: center;
`;
fixedButtonContainer.appendChild(svg);
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M 12,12 L 28,28 M 28,12 12,28');
path.setAttribute('stroke', '#000000');
path.setAttribute('stroke-width', "2px");
path.style.cssText = `
/**filter: drop-shadow(0 0px 1.2px rgba(0,0,0,.7));**/
`;
svg.appendChild(path);
if (debug)
console.log("Created fallback close button", svg, element);
}
appendElement(element, parent) {
if (parent.shadowRoot)
return parent.shadowRoot.appendChild(element);
return parent.appendChild(element);
}
}
//# sourceMappingURL=needle-engine.ar-overlay.js.map