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