UNPKG

@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.

337 lines (336 loc) • 14.1 kB
import { needleLogoOnlySVG } from "../assets/index.js"; import { showBalloonWarning } from "../debug/index.js"; import { hasProLicense } from "../engine_license.js"; import { Mathf } from "../engine_math.js"; import { getParam } from "../engine_utils.js"; const debug = getParam("debugloading"); const debugRendering = getParam("debugloadingrendering"); const debugLicense = getParam("debuglicense"); /** @internal */ export class LoadingElementOptions { className; additionalClasses; } let currentFileProgress = 0; let currentFileName; /** @internal */ export function calculateProgress01(progress) { if (debug) console.log(progress.progress.loaded.toFixed(0) + "/" + progress.progress.total.toFixed(0), progress); const count = progress.count; const total = progress.progress.total; // if the progress event total amount is unknown / not reported // we slowly move the progress bar forward if (total === 0 || total === undefined) { // reset the temp progress when the file has changed if (currentFileName !== progress.name) currentFileProgress = 0; currentFileName = progress.name; // slowly move the progress bar forward currentFileProgress += (1 - currentFileProgress) * .001; if (debug) showBalloonWarning("Loading " + progress.name + " did not report total size"); } else { currentFileProgress = progress.progress.loaded / total; } const prog = progress.index / count + currentFileProgress / count; return Mathf.clamp01(prog); } /** @internal */ export class EngineLoadingView { static LoadingContainerClassName = "loading"; // the raw progress loadingProgress = 0; /** Usually the NeedleEngineHTMLElement */ _element; _progress = 0; _allowCustomLoadingElement = true; _loadingElement; _loadingTextContainer = null; _loadingBar = null; _loadingBarFinishedColor = null; _messageContainer = null; _loadingElementOptions; /** * Creates a new loading view * @param owner the element that will contain the loading view (should be the NeedleEngineHTMLElement) */ constructor(owner, opts) { this._element = owner; this._loadingElementOptions = opts; } async onLoadingBegin(message) { const _element = this._element.shadowRoot || this._element; if (debug) console.warn("Begin Loading"); if (!this._loadingElement) { for (let i = 0; i < _element.children.length; i++) { const el = _element.children[i]; if (el.classList.contains(EngineLoadingView.LoadingContainerClassName)) { if (!this._allowCustomLoadingElement) { if (debug) console.warn("Remove custom loading container"); _element.removeChild(el); continue; } this._loadingElement = this.createLoadingElement(el); } } if (!this._loadingElement) this._loadingElement = this.createLoadingElement(); } this._progress = 0; this.loadingProgress = 0; this._loadingElement.style.display = "flex"; _element.appendChild(this._loadingElement); this.smoothProgressLoop(); this.setMessage(message ?? ""); } onLoadingUpdate(progress, message) { if (!this._loadingElement?.parentNode) { return; } // console.log(callback.name, callback.progress.loaded / callback.progress.total, callback.index + "/" + callback.count); let total01 = 0; if (typeof progress === "number") { total01 = progress; } else { if ("index" in progress) total01 = calculateProgress01(progress); if (!message && "name" in progress) this.setMessage("loading " + progress.name); } this.loadingProgress = total01; if (message) this.setMessage(message); this.updateDisplay(); } onLoadingFinished() { if (debug) console.warn("Finished Loading"); if (!debugRendering) { this.loadingProgress = 1; this.onDoneLoading(); } } setMessage(message) { if (this._messageContainer) { this._messageContainer.innerText = message; } } _progressLoop; smoothProgressLoop() { if (this._progressLoop) return; let dt = 1 / 12; if (debugRendering) { dt = 1 / 500; if (typeof debugRendering === "number") dt *= debugRendering; } this._progressLoop = setInterval(() => { // increase loading speed when almost done if (this.loadingProgress >= .95 && !debugRendering) dt = .9; this._progress = Mathf.lerp(this._progress, this.loadingProgress, dt * this.loadingProgress); this.updateDisplay(); }, dt); } onDoneLoading() { if (this._loadingElement) { if (debug) console.log("Hiding loading element"); // animate alpha to 0 const element = this._loadingElement; element.animate([ { opacity: 1 }, { opacity: 0 } ], { duration: 200, easing: 'ease-in-out', }).addEventListener('finish', () => { element.style.display = "none"; element.remove(); }); } if (this._progressLoop) clearInterval(this._progressLoop); this._progressLoop = null; } updateDisplay() { const t = this._progress; const percent = (t * 100).toFixed(0) + "%"; if (this._loadingBar) { this._loadingBar.style.width = t * 100 + "%"; if (t >= 1 && this._loadingBarFinishedColor) { this._loadingBar.style.background = this._loadingBarFinishedColor; } } if (this._loadingTextContainer) this._loadingTextContainer.textContent = percent; } createLoadingElement(existing) { if (debug && !existing) console.log("Creating loading element"); this._loadingElement = existing || document.createElement("div"); let loadingStyle = this._element.getAttribute("loading-style"); // if nothing is defined OR loadingStyle is set to auto if (!loadingStyle || loadingStyle === "auto") { if (window.matchMedia('(prefers-color-scheme: dark)').matches) loadingStyle = "dark"; else loadingStyle = "light"; } const hasLicense = hasProLicense(); if (!existing) { this._loadingElement.style.position = "absolute"; this._loadingElement.style.width = "100%"; this._loadingElement.style.height = "100%"; this._loadingElement.style.left = "0"; this._loadingElement.style.top = "0"; this._loadingElement.style.overflow = "hidden"; const loadingBackgroundColor = this._element.getAttribute("loading-background"); if (loadingBackgroundColor) { this._loadingElement.style.background = loadingBackgroundColor; } else this._loadingElement.style.backgroundColor = "transparent"; this._loadingElement.style.display = "flex"; this._loadingElement.style.alignItems = "center"; this._loadingElement.style.justifyContent = "center"; this._loadingElement.style.zIndex = "0"; this._loadingElement.style.flexDirection = "column"; this._loadingElement.style.pointerEvents = "none"; this._loadingElement.style.color = "white"; this._loadingElement.style.fontFamily = 'system-ui, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"'; this._loadingElement.style.fontSize = "1rem"; if (loadingStyle === "light") this._loadingElement.style.color = "rgba(0,0,0,.6)"; else this._loadingElement.style.color = "rgba(255,255,255,.3)"; } const className = this._loadingElementOptions?.className ?? EngineLoadingView.LoadingContainerClassName; this._loadingElement.classList.add(className); if (this._loadingElementOptions?.additionalClasses) { for (const c of this._loadingElementOptions.additionalClasses) { this._loadingElement.classList.add(c); } } const content = document.createElement("div"); content.style.cssText = ` position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; pointer-events: none; `; this._loadingElement.appendChild(content); const poster = this._element.getAttribute("poster"); if (poster !== null && poster !== "0") { const backgroundImage = document.createElement("div"); const backgroundBlur = poster?.length ? "0px" : "50px"; backgroundImage.style.cssText = ` position: absolute; left: 0; top: 0; bottom: 0; right: 0; z-index: -1; overflow: hidden; margin: -${backgroundBlur}; background: url('${poster?.length ? poster : "/include/poster.webp"}') center center no-repeat; background-size: cover; filter: blur(${backgroundBlur}); `; this._loadingElement.appendChild(backgroundImage); } const logo = document.createElement("img"); const logoWidth = "80%"; const logoHeight = "15%"; const logoDelay = ".2s"; logo.style.userSelect = "none"; logo.style.objectFit = "contain"; logo.style.transform = "translateY(30px)"; logo.style.opacity = "0.0000001"; logo.style.transition = `transform 1s ease-out ${logoDelay}, opacity .3s ease-in-out ${logoDelay}`; logo.src = needleLogoOnlySVG; let isUsingCustomLogo = false; if (hasLicense && this._element) { const customLogo = this._element.getAttribute("logo-src"); if (customLogo) { isUsingCustomLogo = true; logo.src = customLogo; setTimeout(() => { logo.style.opacity = "1"; logo.style.transform = "translateY(0px)"; }, 1); } } logo.style.width = `${logoWidth}`; logo.style.height = `min(1000px, max(${logoHeight}, 50px))`; content.appendChild(logo); const details = document.createElement("div"); details.style.cssText = ` display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; opacity: 0; transition: opacity 1s ease-in-out 4s; `; setTimeout(() => { details.style.opacity = "1"; }, 1); this._loadingElement.appendChild(details); const loadingBarContainer = document.createElement("div"); const maxWidth = 100; loadingBarContainer.style.display = "flex"; loadingBarContainer.style.width = maxWidth + "%"; loadingBarContainer.style.height = "5px"; loadingBarContainer.style.position = "absolute"; loadingBarContainer.style.left = "0"; loadingBarContainer.style.top = "0px"; loadingBarContainer.style.opacity = "0"; loadingBarContainer.style.transition = "opacity 1s ease-in-out"; loadingBarContainer.style.backgroundColor = "rgba(240,240,240,.5)"; setTimeout(() => { loadingBarContainer.style.opacity = "1"; }, 1); this._loadingElement.appendChild(loadingBarContainer); this._loadingBar = document.createElement("div"); loadingBarContainer.appendChild(this._loadingBar); const getGradientPos = function (t) { return Mathf.lerp(0, maxWidth, t) + "%"; }; // `linear-gradient(90deg, #204f49 ${getGradientPos(0)}, #0BA398 ${getGradientPos(.3)}, #66A22F ${getGradientPos(.6)}, #D7DB0A ${getGradientPos(1)})`; this._loadingBar.style.backgroundAttachment = "fixed"; this._loadingBar.style.background = "#c4c4c4ab"; this._loadingBarFinishedColor = "#ddddddab"; this._loadingBar.style.width = "0%"; this._loadingBar.style.height = "100%"; // this._loadingTextContainer = document.createElement("div"); // this._loadingTextContainer.style.display = "flex"; // this._loadingTextContainer.style.justifyContent = "center"; // this._loadingTextContainer.style.marginTop = ".2rem"; // details.appendChild(this._loadingTextContainer); // const messageContainer = document.createElement("div"); // this._messageContainer = messageContainer; // messageContainer.style.display = "flex"; // messageContainer.style.fontSize = ".8rem"; // messageContainer.style.paddingTop = ".1rem"; // // messageContainer.style.border = "1px solid rgba(255,255,255,.1)"; // messageContainer.style.justifyContent = "center"; // details.appendChild(messageContainer); // if (hasLicense && this._element) { // const loadingTextColor = this._element.getAttribute("loading-text-color"); // if (loadingTextColor) { // messageContainer.style.color = loadingTextColor; // } // } // this.handleRuntimeLicense(this._loadingElement); return this._loadingElement; } } //# sourceMappingURL=needle-engine.loading.js.map