diff --git a/src/app/services/babylon/babylon.service.ts b/src/app/services/babylon/babylon.service.ts index eb8f9dbc..6a7db092 100644 --- a/src/app/services/babylon/babylon.service.ts +++ b/src/app/services/babylon/babylon.service.ts @@ -26,6 +26,12 @@ import { WebGPUEngine, RegisterSceneLoaderPlugin, ISceneLoaderProgressEvent, + CreateGround, + StandardMaterial, + Color3, + DirectionalLight, + ShadowGenerator, + Mesh, } from '@babylonjs/core'; import '@babylonjs/core/Debug/debugLayer'; import '@babylonjs/inspector'; @@ -54,6 +60,7 @@ import { beforeAudioRender } from './strategies/render-strategies'; import { EptImporter } from './importers/ept/ept-importer'; import { CopcImporter } from './importers/copc/copc-importer'; import { LoadingScreenService } from './loadingscreen'; +import { InspectorToken, ShowInspector } from '@babylonjs/inspector'; RegisterSceneLoaderPlugin(new CopcImporter()); RegisterSceneLoaderPlugin(new EptImporter()); @@ -144,11 +151,9 @@ export class BabylonService { public background: { url: string; - color: RGBA; layer: Layer | undefined; } = { url: 'assets/textures/backgrounds/darkgrey.jpg', - color: { r: 0, g: 0, b: 0, a: 0 }, layer: undefined, }; @@ -223,12 +228,59 @@ export class BabylonService { (window as any)['scene'] = () => this.getScene(); } + private inspectorToken?: InspectorToken; public enableInspector() { - this.scene.debugLayer.show(); + this.inspectorToken = ShowInspector(this.scene); } public disableInspector() { - this.scene.debugLayer.hide(); + this.inspectorToken?.dispose(); + } + + private defaultShadowVariables = { + groundDiffuseColor: new Color3(0.45, 0.45, 0.45), + groundSpecularColor: new Color3(0, 0, 0), + groundSpecularPower: 64, + groundEmissiveColor: new Color3(0.0, 0.0, 0.0), + groundAlpha: 0.3, + sceneClearColor: new Color4(0.3345, 0.3345, 0.3345, 1), + }; + /** + * Adds a default shadow generator to the scene with a directional light and a ground plane to receive shadows. + * Intended to be used on the home page with the default model. + */ + public addDefaultShadowGenerator(meshes: AbstractMesh[]) { + const ground = CreateGround( + 'shadowGround', + { width: 50000, height: 50000, subdivisions: 1 }, + this.scene, + ); + ground.setAbsolutePosition(new Vector3(20, 0, 20)); + const mat = new StandardMaterial('shadowGroundMat'); + mat.diffuseColor = this.defaultShadowVariables.groundDiffuseColor; + mat.specularColor = this.defaultShadowVariables.groundSpecularColor; + mat.specularPower = this.defaultShadowVariables.groundSpecularPower; + mat.emissiveColor = this.defaultShadowVariables.groundEmissiveColor; + mat.alpha = this.defaultShadowVariables.groundAlpha; + mat.transparencyMode = StandardMaterial.MATERIAL_ALPHATESTANDBLEND; + ground.material = mat; + ground.receiveShadows = true; + const light = new DirectionalLight('dirLight', new Vector3(-0.05, -0.75, -0.66), this.scene); + light.intensity = 1; + light.position = new Vector3(20, 120, 20); + light.autoUpdateExtends = true; + light.autoCalcShadowZBounds = true; + const shadowGenerator = new ShadowGenerator(1024, light); + shadowGenerator.darkness = 0; + shadowGenerator.usePoissonSampling = true; + + for (const mesh of meshes) { + if (mesh.name === '__root__') continue; + if (mesh === ground) continue; + shadowGenerator.addShadowCaster(mesh); + } + + return shadowGenerator; } public getScene(): Scene { @@ -270,16 +322,17 @@ export class BabylonService { } public setBackgroundColor(color: RGBA): void { - this.background.color = color; + const queryParams = new URLSearchParams(location.search); + if (queryParams.get('transparent')) { + this.scene.clearColor = this.defaultShadowVariables.sceneClearColor; + return; + } + this.scene.clearColor = this.isTransparent() ? new Color4(0, 0, 0, 0) : new Color4(color.r / 255, color.g / 255, color.b / 255, color.a); } - public getColor(): any { - return this.background.color; - } - public hideMesh(tag: string, visibility: boolean) { this.scene.getMeshesByTags(tag, mesh => (mesh.isVisible = visibility)); } diff --git a/src/app/services/entitysettings/entitysettings.service.ts b/src/app/services/entitysettings/entitysettings.service.ts index fbb4af59..ca5805a5 100644 --- a/src/app/services/entitysettings/entitysettings.service.ts +++ b/src/app/services/entitysettings/entitysettings.service.ts @@ -24,6 +24,7 @@ import { createWorldAxis, } from './visualUIHelper'; import { LoadingScreenService } from '../babylon/loadingscreen'; +import { PostMessageService } from '../post-message/post-message.service'; const isDegreeSpectrum = (value: number) => { return value >= 0 && value <= 360 ? value : value > 360 ? 360 : 0; @@ -64,6 +65,7 @@ export class EntitySettingsService { private lights: LightService, private annotationService: AnnotationService, private loadingScreen: LoadingScreenService, + private postMessage: PostMessageService, ) { this.processing.state$ .pipe( @@ -77,7 +79,13 @@ export class EntitySettingsService { this.setUpSettings() .then(() => console.log('Settings loaded')) .catch((err: Error) => console.log('Settings not loaded', err.message)) - .finally(() => this.loadingScreen.hide()), + .finally(() => { + this.loadingScreen.hide(); + this.postMessage.sendToParent({ + type: 'settingsLoaded', + data: {}, + }); + }), ); }); } diff --git a/src/app/services/processing/processing.service.ts b/src/app/services/processing/processing.service.ts index 2a30d72b..01c61d27 100644 --- a/src/app/services/processing/processing.service.ts +++ b/src/app/services/processing/processing.service.ts @@ -499,14 +499,20 @@ export class ProcessingService { }); } - public loadDefaultEntityData() { + public async loadDefaultEntityData() { this.updateEntityQuality('low'); - this.loadEntity(defaultEntity as IEntity, ''); + return this.loadEntity(defaultEntity as IEntity, '').then(meshes => { + if (!meshes) return; + const queryParams = new URLSearchParams(location.search); + if (queryParams.get('transparent')) { + this.babylon.addDefaultShadowGenerator(meshes); + } + }); } - public loadFallbackEntity() { + public async loadFallbackEntity() { this.updateEntityQuality('low'); - this.loadEntity(fallbackEntity as IEntity, ''); + return this.loadEntity(fallbackEntity as IEntity, ''); } public fetchAndLoad(entityId?: string | null, compilationId?: string | null) { @@ -623,7 +629,10 @@ export class ProcessingService { }); } - public async loadEntity(newEntity: IEntity, overrideUrl?: string) { + public async loadEntity( + newEntity: IEntity, + overrideUrl?: string, + ): Promise { const mode = this.mode$.getValue(); const baseURL = overrideUrl ?? environment.server_url; if (this.loadingScreen.isLoading() || !newEntity.processed || !newEntity.mediaType) { @@ -656,7 +665,7 @@ export class ProcessingService { const isDefault = newEntity._id === 'default'; this.loadingScreen.show(); - this.babylon + return this.babylon .loadEntity( true, isAudio ? 'assets/models/kompakkt.babylon' : url, @@ -669,11 +678,12 @@ export class ProcessingService { }) .then(meshes => { this.updateActiveEntity(newEntity, meshes); + return meshes; }) .catch(error => { console.error('Failed to load entity from server', error); this.message.error('Failed to load entity from server'); - this.loadFallbackEntity(); + return this.loadFallbackEntity(); }); } }