Skip to content

Commit

Permalink
implements customization for the loading screen (#182)
Browse files Browse the repository at this point in the history
* implements customization for the loading screen

* replaces dev address accidentally left on client index

* Loading screen styling

* overlay styling

---------

Co-authored-by: Marcus Longmuir <[email protected]>
  • Loading branch information
TheCodeTherapy and MarcusLongmuir authored Nov 21, 2024
1 parent 2ed8f19 commit 19bc481
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 55 deletions.
Binary file added example/assets/images/loading-bg.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions example/multi-user-3d-web-experience/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Networked3dWebExperienceClient } from "@mml-io/3d-web-experience-client";

import hdrJpgUrl from "../../../assets/hdr/puresky_2k.jpg";
import loadingBackground from "../../../assets/images/loading-bg.jpg";
import airAnimationFileUrl from "../../../assets/models/anim_air.glb";
import doubleJumpAnimationFileUrl from "../../../assets/models/anim_double_jump.glb";
import idleAnimationFileUrl from "../../../assets/models/anim_idle.glb";
Expand Down Expand Up @@ -33,6 +34,14 @@ const app = new Networked3dWebExperienceClient(holder, {
avatarConfiguration: {
availableAvatars: [],
},
loadingScreen: {
background: "#424242",
color: "#ffffff",
backgroundImageUrl: loadingBackground,
backgroundBlurAmount: 12,
title: "3D Web Experience",
subtitle: "Powered by Metaverse Markup Language",
},
});

app.update();
2 changes: 1 addition & 1 deletion packages/3d-web-client-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ export { TimeManager } from "./time/TimeManager";
export { CollisionsManager } from "./collisions/CollisionsManager";
export { Sun } from "./sun/Sun";
export { GroundPlane } from "./ground-plane/GroundPlane";
export { LoadingScreen } from "./loading-screen/LoadingScreen";
export { LoadingScreenConfig, LoadingScreen } from "./loading-screen/LoadingScreen";
export { ErrorScreen } from "./error-screen/ErrorScreen";
export { EnvironmentConfiguration } from "./rendering/composer";
236 changes: 183 additions & 53 deletions packages/3d-web-client-core/src/loading-screen/LoadingScreen.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { LoadingProgressManager } from "mml-web";

export type LoadingScreenConfig = {
background?: string;
backgroundImageUrl?: string;
backgroundBlurAmount?: number;
overlayLayers?: Array<{
overlayImageUrl: string;
overlayAnchor: "top-left" | "top-right" | "bottom-left" | "bottom-right";
overlayOffset?: { x: number; y: number };
}>;
title?: string;
subtitle?: string;
color?: string;
};

export class LoadingScreen {
public readonly element: HTMLDivElement;

private loadingBannerText: HTMLDivElement;
private readonly backgroundBlur: HTMLDivElement;

private overlayLayers: HTMLDivElement[] = [];

private loadingBanner: HTMLDivElement;
private loadingBannerTitle: HTMLDivElement;
private loadingBannerSubtitle: HTMLDivElement;

private progressBarBackground: HTMLDivElement;
private progressBarHolder: HTMLDivElement;
Expand All @@ -21,57 +41,148 @@ export class LoadingScreen {
private loadingCallback: () => void;
private disposed: boolean = false;

constructor(private loadingProgressManager: LoadingProgressManager) {
constructor(
private loadingProgressManager: LoadingProgressManager,
private config?: LoadingScreenConfig,
) {
const defaultBackground = "linear-gradient(45deg, #28284B 0%, #303056 100%)";
this.element = document.createElement("div");
this.element.id = "loading-screen";

this.element.style.position = "absolute";
this.element.style.top = "0";
this.element.style.left = "0";
this.element.style.width = "100%";
this.element.style.height = "100%";
this.element.style.background = "linear-gradient(45deg, #28284B 0%, #303056 100%)";
this.element.style.color = "white";
this.element.addEventListener("click", (event) => {
event.stopPropagation();
});
this.element.addEventListener("mousedown", (event) => {
event.stopPropagation();
});
this.element.addEventListener("mousemove", (event) => {
event.stopPropagation();
});
this.element.addEventListener("mouseup", (event) => {
event.stopPropagation();
});
this.element.style.backgroundColor = this.config?.background || defaultBackground;
this.element.style.background = this.config?.background || defaultBackground;
this.element.style.zIndex = "10001";

this.backgroundBlur = document.createElement("div");
this.backgroundBlur.id = "loading-screen-blur";
this.backgroundBlur.style.position = "absolute";
this.backgroundBlur.style.top = "0";
this.backgroundBlur.style.left = "0";
this.backgroundBlur.style.width = "100%";
this.backgroundBlur.style.height = "100%";
this.backgroundBlur.style.display = "flex";
if (this.config?.backgroundBlurAmount) {
this.backgroundBlur.style.backdropFilter = `blur(${this.config.backgroundBlurAmount}px)`;
}
this.element.append(this.backgroundBlur);

if (this.config?.backgroundImageUrl) {
this.element.style.backgroundImage = `url(${this.config.backgroundImageUrl})`;
this.element.style.backgroundPosition = "center";
this.element.style.backgroundSize = "cover";
}

if (this.config?.overlayLayers) {
const logLoadError = (imageUrl: string) => {
console.error(`Failed to load overlay image: ${imageUrl}`);
};

for (const layer of this.config.overlayLayers) {
const overlayLayer = document.createElement("div");
overlayLayer.style.position = "absolute";
overlayLayer.style.background = `url(${layer.overlayImageUrl}) no-repeat`;
overlayLayer.style.backgroundSize = "contain";

const anchor = layer.overlayAnchor;
const offsetX = layer.overlayOffset?.x || 0;
const offsetY = layer.overlayOffset?.y || 0;

if (anchor.includes("top")) {
overlayLayer.style.top = `${offsetY}px`;
} else if (anchor.includes("bottom")) {
overlayLayer.style.bottom = `${offsetY}px`;
}

if (anchor.includes("left")) {
overlayLayer.style.left = `${offsetX}px`;
} else if (anchor.includes("right")) {
overlayLayer.style.right = `${offsetX}px`;
}

const image = new Image();
image.src = layer.overlayImageUrl;
image.onload = () => {
const naturalWidth = image.naturalWidth;
const naturalHeight = image.naturalHeight;

overlayLayer.style.width = `${naturalWidth}px`;
overlayLayer.style.height = `${naturalHeight}px`;
};

image.onerror = () => logLoadError(layer.overlayImageUrl);

this.overlayLayers.push(overlayLayer);
this.backgroundBlur.append(overlayLayer);
}
}

this.element.style.color = this.config?.color || "white";

this.loadingBanner = document.createElement("div");
this.loadingBanner.style.position = "absolute";
this.loadingBanner.style.display = "flex";
this.loadingBanner.style.flexDirection = "column";
this.loadingBanner.style.left = "0";
this.loadingBanner.style.bottom = "0";
this.loadingBanner.style.padding = "0";
this.loadingBanner.style.width = "100%";
this.loadingBanner.style.justifyContent = "flex-end";
this.backgroundBlur.append(this.loadingBanner);

this.loadingBannerText = document.createElement("div");
this.loadingBannerText.textContent = "Loading...";
this.loadingBannerText.style.position = "absolute";
this.loadingBannerText.style.display = "flex";
this.loadingBannerText.style.top = "0";
this.loadingBannerText.style.left = "0";
this.loadingBannerText.style.width = "100%";
this.loadingBannerText.style.height = "100%";
this.loadingBannerText.style.color = "white";
this.loadingBannerText.style.fontSize = "80px";
this.loadingBannerText.style.fontWeight = "bold";
this.loadingBannerText.style.fontFamily = "sans-serif";
this.loadingBannerText.style.alignItems = "center";
this.loadingBannerText.style.justifyContent = "center";
this.element.append(this.loadingBannerText);
if (this.config?.title) {
this.loadingBannerTitle = document.createElement("div");
this.loadingBannerTitle.textContent = this.config.title;
this.loadingBannerTitle.style.color = this.config?.color || "white";
this.loadingBannerTitle.style.paddingLeft = "40px";
this.loadingBannerTitle.style.paddingRight = "40px";
this.loadingBannerTitle.style.fontSize = "42px";
this.loadingBannerTitle.style.fontWeight = "bold";
this.loadingBannerTitle.style.fontFamily = "sans-serif";
if (this.config?.background) {
this.loadingBannerTitle.style.textShadow = `0px 0px 80px ${this.config.background}`;
}
this.loadingBanner.append(this.loadingBannerTitle);
}

if (this.config?.subtitle) {
this.loadingBannerSubtitle = document.createElement("div");
this.loadingBannerSubtitle.style.color = this.config?.color || "white";
this.loadingBannerSubtitle.style.paddingLeft = "40px";
this.loadingBannerSubtitle.style.paddingRight = "40px";
this.loadingBannerSubtitle.style.fontSize = "16px";
this.loadingBannerSubtitle.style.fontWeight = "400";
this.loadingBannerSubtitle.style.fontFamily = "sans-serif";
this.loadingBannerSubtitle.style.marginTop = "12px";
if (this.config?.background) {
this.loadingBannerSubtitle.style.textShadow = `0px 0px 40px ${this.config.background}`;
}

this.loadingBannerSubtitle.textContent = this.config.subtitle;
this.loadingBanner.append(this.loadingBannerSubtitle);
}

this.progressDebugViewHolder = document.createElement("div");
this.progressDebugViewHolder.style.display = "flex";
this.progressDebugViewHolder.style.display = "none";
this.progressDebugViewHolder.style.position = "absolute";
this.progressDebugViewHolder.style.maxHeight = "calc(100% - 74px)";
this.progressDebugViewHolder.style.left = "0";
this.progressDebugViewHolder.style.bottom = "74px";
this.progressDebugViewHolder.style.width = "100%";
this.progressDebugViewHolder.style.width = "calc(100% - 80px)";
this.progressDebugViewHolder.style.maxHeight = "calc(100% - 120px)";
this.progressDebugViewHolder.style.left = "40px";
this.progressDebugViewHolder.style.bottom = "60px";
this.progressDebugViewHolder.style.alignItems = "center";
this.progressDebugViewHolder.style.justifyContent = "center";
this.progressDebugViewHolder.style.zIndex = "10003";
this.element.append(this.progressDebugViewHolder);

this.progressDebugView = document.createElement("div");
this.progressDebugView.style.backgroundColor = "rgba(128, 128, 128, 0.25)";
this.progressDebugView.style.backgroundColor = "rgba(128, 128, 128, 0.5)";
this.progressDebugView.style.border = "1px solid black";
this.progressDebugView.style.borderRadius = "7px";
this.progressDebugView.style.width = "100%";
this.progressDebugView.style.maxWidth = "100%";
this.progressDebugView.style.overflow = "auto";
this.progressDebugViewHolder.append(this.progressDebugView);
Expand All @@ -81,6 +192,8 @@ export class LoadingScreen {
this.debugCheckbox.checked = false;
this.debugCheckbox.addEventListener("change", () => {
this.progressDebugElement.style.display = this.debugCheckbox.checked ? "block" : "none";
this.loadingBannerTitle.style.display = this.debugCheckbox.checked ? "none" : "flex";
this.loadingBannerSubtitle.style.display = this.debugCheckbox.checked ? "none" : "flex";
if (this.hasCompleted) {
this.dispose();
}
Expand All @@ -102,24 +215,37 @@ export class LoadingScreen {

this.progressBarHolder = document.createElement("div");
this.progressBarHolder.style.display = "flex";
this.progressBarHolder.style.alignItems = "center";
this.progressBarHolder.style.justifyContent = "center";
this.progressBarHolder.style.position = "absolute";
this.progressBarHolder.style.bottom = "20px";
this.progressBarHolder.style.left = "0";
this.progressBarHolder.style.alignItems = "start";
this.progressBarHolder.style.justifyContent = "flex-start";
this.progressBarHolder.style.width = "100%";
this.element.append(this.progressBarHolder);
this.progressBarHolder.style.marginLeft = "40px";
this.progressBarHolder.style.marginBottom = "40px";
this.progressBarHolder.style.cursor = "pointer";
this.progressBarHolder.style.marginTop = "24px";
this.loadingBanner.append(this.progressBarHolder);

this.progressBarBackground = document.createElement("div");
this.progressBarBackground.style.position = "relative";
this.progressBarBackground.style.width = "500px";
this.progressBarBackground.style.maxWidth = "80%";
this.progressBarBackground.style.backgroundColor = "gray";
this.progressBarBackground.style.height = "50px";
this.progressBarBackground.style.lineHeight = "50px";
this.progressBarBackground.style.borderRadius = "25px";
this.progressBarBackground.style.border = "2px solid white";
this.progressBarBackground.style.width = "80%";
this.progressBarBackground.style.maxWidth = "400px";
this.progressBarBackground.style.minWidth = "240px";
this.progressBarBackground.style.backgroundColor = "rgba(32,32,32, 0.25)";
this.progressBarBackground.style.backdropFilter = "blur(4px)";
this.progressBarBackground.style.height = "16px";
this.progressBarBackground.style.lineHeight = "16px";
this.progressBarBackground.style.borderRadius = "16px";
this.progressBarBackground.style.overflow = "hidden";
this.progressBarBackground.addEventListener("click", () => {
const display = this.progressDebugViewHolder.style.display;
if (display === "none") {
this.progressDebugViewHolder.style.display = "flex";
} else {
this.progressDebugViewHolder.style.display = "none";
this.debugCheckbox.checked = false;
this.progressDebugElement.style.display = this.debugCheckbox.checked ? "block" : "none";
this.loadingBannerTitle.style.display = this.debugCheckbox.checked ? "none" : "flex";
}
});
this.progressBarHolder.append(this.progressBarBackground);

this.progressBar = document.createElement("div");
Expand All @@ -128,7 +254,8 @@ export class LoadingScreen {
this.progressBar.style.left = "0";
this.progressBar.style.width = "0";
this.progressBar.style.height = "100%";
this.progressBar.style.backgroundColor = "#0050a4";
this.progressBar.style.pointerEvents = "none";
this.progressBar.style.backgroundColor = this.config?.color || "#0050a4";
this.progressBarBackground.append(this.progressBar);

this.loadingStatusText = document.createElement("div");
Expand All @@ -137,11 +264,14 @@ export class LoadingScreen {
this.loadingStatusText.style.left = "0";
this.loadingStatusText.style.width = "100%";
this.loadingStatusText.style.height = "100%";
this.loadingStatusText.style.color = "white";
this.loadingStatusText.style.color = "rgba(200,200,200,0.9)";
this.loadingStatusText.style.fontSize = "10px";
this.loadingStatusText.style.textAlign = "center";
this.loadingStatusText.style.verticalAlign = "middle";
this.loadingStatusText.style.mixBlendMode = "difference";
this.loadingStatusText.style.fontFamily = "sans-serif";
this.loadingStatusText.style.fontWeight = "bold";
this.loadingStatusText.style.userSelect = "none";
this.loadingStatusText.textContent = "Loading...";
this.progressBarBackground.append(this.loadingStatusText);

Expand All @@ -157,7 +287,7 @@ export class LoadingScreen {
this.loadingStatusText.textContent = "Completed";
this.progressBar.style.width = "100%";
} else {
this.loadingStatusText.textContent = `Loading... ${(loadingRatio * 100).toFixed(2)}%`;
this.loadingStatusText.textContent = `${(loadingRatio * 100).toFixed(2)}%`;
this.progressBar.style.width = `${loadingRatio * 100}%`;
}
this.progressDebugElement.textContent = LoadingProgressManager.LoadingProgressSummaryToString(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
GroundPlane,
KeyInputManager,
LoadingScreen,
LoadingScreenConfig,
MMLCompositionScene,
TimeManager,
TweakPane,
Expand Down Expand Up @@ -81,6 +82,7 @@ export type Networked3dWebExperienceClientConfig = {
voiceChatAddress?: string;
updateURLLocation?: boolean;
onServerBroadcast?: (broadcast: { broadcastType: string; payload: any }) => void;
loadingScreen?: LoadingScreenConfig;
} & UpdatableConfig;

export type UpdatableConfig = {
Expand Down Expand Up @@ -276,7 +278,7 @@ export class Networked3dWebExperienceClient {

this.setupMMLScene();

this.loadingScreen = new LoadingScreen(this.loadingProgressManager);
this.loadingScreen = new LoadingScreen(this.loadingProgressManager, this.config.loadingScreen);
this.element.append(this.loadingScreen.element);

this.loadingProgressManager.addProgressCallback(() => {
Expand Down

0 comments on commit 19bc481

Please sign in to comment.