diff --git a/packages/tools/sandbox/src/components/footer.tsx b/packages/tools/sandbox/src/components/footer.tsx index ee7b04b9ce61..bf4a532395d1 100644 --- a/packages/tools/sandbox/src/components/footer.tsx +++ b/packages/tools/sandbox/src/components/footer.tsx @@ -14,8 +14,10 @@ import babylonIdentity from "../img/babylon-identity.svg"; import iconEdit from "../img/icon-edit.svg"; import iconOpen from "../img/icon-open.svg"; import iconIBL from "../img/icon-ibl.svg"; +import iconOpenPBR from "../img/icon-openpbr.png"; import iconCameras from "../img/icon-cameras.svg"; import iconVariants from "../img/icon-variants.svg"; +import { LocalStorageHelper } from "../tools/localStorageHelper"; interface IFooterProps { globalState: GlobalState; @@ -74,6 +76,17 @@ export class Footer extends React.Component { return this.props.globalState?.glTFLoaderExtensions["KHR_materials_variants"] as KHR_materials_variants; } + private _toggleUseOpenPBR(): void { + this.props.globalState.useOpenPBR = !this.props.globalState.useOpenPBR; + LocalStorageHelper.SetUseOpenPBR(this.props.globalState.useOpenPBR); + + if (this.props.globalState.currentScene) { + this.props.globalState.onRequestSceneReload.notifyObservers(); + } + + this.forceUpdate(); + } + override render() { let variantNames: string[] = []; let hasVariants = false; @@ -151,6 +164,14 @@ export class Footer extends React.Component { enabled={!!this.props.globalState.currentScene} searchPlaceholder="Search environment" /> + this._toggleUseOpenPBR()} + enabled={true} + active={this.props.globalState.useOpenPBR} + /> void; icon: any; label: string; + active?: boolean; } export class FooterButton extends React.Component { @@ -16,7 +17,7 @@ export class FooterButton extends React.Component { } return ( -
this.props.onClick()}> +
this.props.onClick()}> {this.props.label}
); diff --git a/packages/tools/sandbox/src/components/renderingZone.tsx b/packages/tools/sandbox/src/components/renderingZone.tsx index 680ee48f175c..d255060703cf 100644 --- a/packages/tools/sandbox/src/components/renderingZone.tsx +++ b/packages/tools/sandbox/src/components/renderingZone.tsx @@ -25,6 +25,7 @@ import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; import { type AbstractEngine } from "core/Engines/abstractEngine"; import { setOpenGLOrientationForUV, useOpenGLOrientationForUV } from "core/Compat/compatibilityOptions"; import { ImageProcessingConfiguration } from "core/Materials/imageProcessingConfiguration"; +import { type Observer } from "core/Misc/observable"; function GetFileExtension(str: string): string { return str.split(".").pop() || ""; @@ -60,6 +61,7 @@ export class RenderingZone extends React.Component { private _scene: Scene; private _canvas: HTMLCanvasElement; private _restoreInspector = false; + private _onRequestSceneReloadObserver: Observer | null = null; public constructor(props: IRenderingZoneProps) { super(props); @@ -120,10 +122,7 @@ export class RenderingZone extends React.Component { } }, () => { - // Ensure we stop any existing render loop when reloading, because if there was a previous scene loaded from the URL - // the filesInput will not know about it, and so it won't call stopRenderLoop. - this._engine.stopRenderLoop(); - filesInput.reload(); + this._reloadCurrentAsset(); }, (file, scene, message) => { this.props.globalState.onError.notifyObservers({ message: message }); @@ -180,15 +179,30 @@ export class RenderingZone extends React.Component { window.addEventListener("keydown", (event) => { // Press R to reload if (event.keyCode === 82 && event.target && (event.target as HTMLElement).nodeName !== "INPUT" && this._scene) { - if (this.props.globalState.assetUrl) { - this.loadAssetFromUrl(this.props.globalState.assetUrl); - } else { - filesInput.reload(); - } + this._reloadCurrentAsset(); + } + }); + + this._onRequestSceneReloadObserver = this.props.globalState.onRequestSceneReload.add(() => { + if (this._scene) { + this._reloadCurrentAsset(); } }); } + private _reloadCurrentAsset(): void { + // Ensure we stop any existing render loop when reloading, because if there was a previous scene loaded from the URL + // filesInput will not know about it, and so it won't call stopRenderLoop. + this._engine.stopRenderLoop(); + + if (this.props.globalState.assetUrl) { + this.loadAssetFromUrl(this.props.globalState.assetUrl); + return; + } + + this.props.globalState.filesInput.reload(); + } + prepareCamera() { let camera = this._scene.activeCamera as ArcRotateCamera; // Attach camera to canvas inputs @@ -420,6 +434,7 @@ export class RenderingZone extends React.Component { if (this._currentPluginName === "gltf") { const loader = plugin as GLTFFileLoader; loader.transparencyAsCoverage = this.props.globalState.commerceMode; + loader.useOpenPBR = this.props.globalState.useOpenPBR; loader.validate = true; @@ -438,6 +453,13 @@ export class RenderingZone extends React.Component { this.initEngine(); } + public override componentWillUnmount(): void { + if (this._onRequestSceneReloadObserver) { + this.props.globalState.onRequestSceneReload.remove(this._onRequestSceneReloadObserver); + this._onRequestSceneReloadObserver = null; + } + } + override shouldComponentUpdate(nextProps: IRenderingZoneProps) { if (nextProps.expanded !== this.props.expanded) { setTimeout(() => this._engine.resize()); diff --git a/packages/tools/sandbox/src/custom.d.ts b/packages/tools/sandbox/src/custom.d.ts index 874e0cd2ce2e..d07584256e63 100644 --- a/packages/tools/sandbox/src/custom.d.ts +++ b/packages/tools/sandbox/src/custom.d.ts @@ -2,3 +2,13 @@ declare module "*.svg" { const content: string; export default content; } + +declare module "*.scss" { + const content: string; + export default content; +} + +declare module "*.png" { + const content: string; + export default content; +} diff --git a/packages/tools/sandbox/src/globalState.ts b/packages/tools/sandbox/src/globalState.ts index 795a3f15a56a..88dbb1a49018 100644 --- a/packages/tools/sandbox/src/globalState.ts +++ b/packages/tools/sandbox/src/globalState.ts @@ -12,6 +12,7 @@ export class GlobalState { public onEnvironmentChanged = new Observable(); public onRequestClickInterceptor = new Observable(); public onClickInterceptorClicked = new Observable(); + public onRequestSceneReload = new Observable(); public glTFLoaderExtensions: { [key: string]: import("loaders/glTF/index").IGLTFLoaderExtension } = {}; public onFilesInputReady = new Observable(); @@ -19,6 +20,7 @@ export class GlobalState { public isDebugLayerEnabled = false; public commerceMode = false; + public useOpenPBR = false; public assetUrl?: string; public autoRotate = false; diff --git a/packages/tools/sandbox/src/img/icon-openpbr.png b/packages/tools/sandbox/src/img/icon-openpbr.png new file mode 100644 index 000000000000..5d4d731bc652 Binary files /dev/null and b/packages/tools/sandbox/src/img/icon-openpbr.png differ diff --git a/packages/tools/sandbox/src/sandbox.tsx b/packages/tools/sandbox/src/sandbox.tsx index f1dfe9ad97eb..49358bcdd1a3 100644 --- a/packages/tools/sandbox/src/sandbox.tsx +++ b/packages/tools/sandbox/src/sandbox.tsx @@ -104,6 +104,7 @@ export class Sandbox extends React.Component< public constructor(props: ISandboxProps) { super(props); this._globalState = new GlobalState({ version: props.version, bundles: props.bundles }); + this._globalState.useOpenPBR = LocalStorageHelper.GetUseOpenPBR(); this._logoRef = React.createRef(); this._dropTextRef = React.createRef(); this._clickInterceptorRef = React.createRef(); diff --git a/packages/tools/sandbox/src/tools/localStorageHelper.ts b/packages/tools/sandbox/src/tools/localStorageHelper.ts index 952492cb0e7a..139222f93e51 100644 --- a/packages/tools/sandbox/src/tools/localStorageHelper.ts +++ b/packages/tools/sandbox/src/tools/localStorageHelper.ts @@ -1,4 +1,5 @@ const WelcomeDialogDismissedKey = "WelcomeDialogDismissed"; +const UseOpenPBRKey = "UseOpenPBR"; export class LocalStorageHelper { public static ReadLocalStorageValue(key: string, defaultValue: number) { @@ -30,4 +31,20 @@ export class LocalStorageHelper { public static ClearWelcomeDialogDismissed(): void { localStorage.removeItem(WelcomeDialogDismissedKey); } + + /** + * Sets whether glTF loading should force OpenPBR materials. + * @param value The value to persist. + */ + public static SetUseOpenPBR(value: boolean): void { + localStorage.setItem(UseOpenPBRKey, value ? "true" : "false"); + } + + /** + * Gets whether glTF loading should force OpenPBR materials. + * @returns true when OpenPBR mode is enabled. + */ + public static GetUseOpenPBR(): boolean { + return localStorage.getItem(UseOpenPBRKey) === "true"; + } }