import { GlobalState } from "../../globalState" ; import { NodeMaterial } from "babylonjs/Materials/Node/nodeMaterial" ; import { Nullable } from "babylonjs/types" ; import { Observer } from "babylonjs/Misc/observable" ; import { Engine } from "babylonjs/Engines/engine" ; import { Scene } from "babylonjs/scene" ; import { Vector3 } from "babylonjs/Maths/math.vector" ; import { HemisphericLight } from "babylonjs/Lights/hemisphericLight" ; import { ArcRotateCamera } from "babylonjs/Cameras/arcRotateCamera" ; import { PreviewType } from "./previewType" ; import { Animation } from "babylonjs/Animations/animation" ; import { SceneLoader } from "babylonjs/Loading/sceneLoader" ; import { TransformNode } from "babylonjs/Meshes/transformNode" ; import { AbstractMesh } from "babylonjs/Meshes/abstractMesh" ; import { FramingBehavior } from "babylonjs/Behaviors/Cameras/framingBehavior" ; import { DirectionalLight } from "babylonjs/Lights/directionalLight" ; import { LogEntry } from "../log/logComponent" ; import { PointerEventTypes } from "babylonjs/Events/pointerEvents" ; import { Color3 , Color4 } from "babylonjs/Maths/math.color" ; import { PostProcess } from "babylonjs/PostProcesses/postProcess" ; import { Constants } from "babylonjs/Engines/constants" ; import { CurrentScreenBlock } from "babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock" ; import { NodeMaterialModes } from "babylonjs/Materials/Node/Enums/nodeMaterialModes" ; import { ParticleSystem } from "babylonjs/Particles/particleSystem" ; import { IParticleSystem } from "babylonjs/Particles/IParticleSystem" ; import { ParticleHelper } from "babylonjs/Particles/particleHelper" ; import { Texture } from "babylonjs/Materials/Textures/texture" ; import { ParticleTextureBlock } from "babylonjs/Materials/Node/Blocks/Particle/particleTextureBlock" ; import { ReadFile } from "babylonjs/Misc/fileTools" ; import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture" ; import { StandardMaterial } from "babylonjs/Materials/standardMaterial" ; import { Layer } from "babylonjs/Layers/layer" ; import { DataStorage } from "babylonjs/Misc/dataStorage" ; import { NodeMaterialBlock } from "babylonjs/Materials/Node/nodeMaterialBlock" ; import { CreateGround } from "babylonjs/Meshes/Builders/groundBuilder" ; import { CreateSphere } from "babylonjs/Meshes/Builders/sphereBuilder" ; import { CreateTorus } from "babylonjs/Meshes/Builders/torusBuilder" ; export class PreviewManager { private _nodeMaterial : NodeMaterial ; private _onBuildObserver : Nullable < Observer < NodeMaterial > > ; private _onPreviewCommandActivatedObserver : Nullable < Observer < boolean > > ; private _onAnimationCommandActivatedObserver : Nullable < Observer < void > > ; private _onUpdateRequiredObserver : Nullable < Observer < Nullable < NodeMaterialBlock > > > ; private _onPreviewBackgroundChangedObserver : Nullable < Observer < void > > ; private _onBackFaceCullingChangedObserver : Nullable < Observer < void > > ; private _onDepthPrePassChangedObserver : Nullable < Observer < void > > ; private _onLightUpdatedObserver : Nullable < Observer < void > > ; private _engine : Engine ; private _scene : Scene ; private _meshes : AbstractMesh [ ] ; private _camera : ArcRotateCamera ; private _material : NodeMaterial | StandardMaterial ; private _globalState : GlobalState ; private _currentType : number ; private _lightParent : TransformNode ; private _postprocess : Nullable < PostProcess > ; private _proceduralTexture : Nullable < ProceduralTexture > ; private _particleSystem : Nullable < IParticleSystem > ; private _layer : Nullable < Layer > ; public constructor ( targetCanvas , globalState ) { this . _nodeMaterial = globalState . nodeMaterial ; this . _globalState = globalState ; this . _onBuildObserver = this . _nodeMaterial . onBuildObservable . add ( ( nodeMaterial ) => { let serializationObject = nodeMaterial . serialize ( ) ; this . _updatePreview ( serializationObject ) ; } ) ; this . _onPreviewCommandActivatedObserver = globalState . onPreviewCommandActivated . add ( ( forceRefresh ) => { if ( forceRefresh ) { this . _currentType = - 1 ; this . _scene . disableDepthRenderer ( ) ; } this . _refreshPreviewMesh ( ) ; } ) ; this . _onLightUpdatedObserver = globalState . onLightUpdated . add ( ( ) => { this . _prepareLights ( ) ; } ) ; this . _onUpdateRequiredObserver = globalState . onUpdateRequiredObservable . add ( ( ) => { let serializationObject = this . _nodeMaterial . serialize ( ) ; this . _updatePreview ( serializationObject ) ; } ) ; this . _onPreviewBackgroundChangedObserver = globalState . onPreviewBackgroundChanged . add ( ( ) => { this . _scene . clearColor = this . _globalState . backgroundColor ; } ) ; this . _onAnimationCommandActivatedObserver = globalState . onAnimationCommandActivated . add ( ( ) => { this . _handleAnimations ( ) ; } ) ; this . _onBackFaceCullingChangedObserver = globalState . onBackFaceCullingChanged . add ( ( ) => { this . _material . backFaceCulling = this . _globalState . backFaceCulling ; } ) ; this . _onDepthPrePassChangedObserver = globalState . onDepthPrePassChanged . add ( ( ) => { this . _material . needDepthPrePass = this . _globalState . depthPrePass ; } ) ; this . _engine = new Engine ( targetCanvas , true ) ; this . _scene = new Scene ( this . _engine ) ; this . _scene . clearColor = this . _globalState . backgroundColor ; this . _scene . ambientColor = new Color3 ( 1 , 1 , 1 ) ; this . _camera = new ArcRotateCamera ( "Camera" , 0 , 0.8 , 4 , Vector3 . Zero ( ) , this . _scene ) ; this . _camera . lowerRadiusLimit = 3 ; this . _camera . upperRadiusLimit = 10 ; this . _camera . wheelPrecision = 20 ; this . _camera . minZ = 0.1 ; this . _camera . attachControl ( false ) ; this . _lightParent = new TransformNode ( "LightParent" , this . _scene ) ; this . _refreshPreviewMesh ( ) ; this . _engine . runRenderLoop ( ( ) => { this . _engine . resize ( ) ; this . _scene . render ( ) ; } ) ; let lastOffsetX = undefined ; const lightRotationSpeed = 0.01 ; this . _scene . onPointerObservable . add ( ( evt ) => { if ( this . _globalState . controlCamera ) { return ; } if ( evt . type === PointerEventTypes . POINTERUP ) { lastOffsetX = undefined ; return ; } if ( evt . event . buttons !== 1 ) { return ; } if ( lastOffsetX === undefined ) { lastOffsetX = evt . event . offsetX ; } var rotateLighting = ( lastOffsetX - evt . event . offsetX ) * lightRotationSpeed ; this . _lightParent . rotation . y += rotateLighting ; lastOffsetX = evt . event . offsetX ; } ) ; } private _handleAnimations ( ) { this . _scene . stopAllAnimations ( ) ; if ( this . _globalState . rotatePreview ) { for ( var root of this . _scene . rootNodes ) { let transformNode = root as TransformNode ; if ( transformNode . getClassName ( ) === "TransformNode" || transformNode . getClassName ( ) === "Mesh" || transformNode . getClassName ( ) === "GroundMesh" ) { if ( transformNode . rotationQuaternion ) { transformNode . rotation = transformNode . rotationQuaternion . toEulerAngles ( ) ; transformNode . rotationQuaternion = null ; } Animation . CreateAndStartAnimation ( "turnTable" , root , "rotation.y" , 60 , 1200 , transformNode . rotation . y , transformNode . rotation . y + 2 * Math . PI , 1 ) ; } } } } private _prepareLights ( ) { let currentLights = this . _scene . lights . slice ( 0 ) ; for ( var light of currentLights ) { light . dispose ( ) ; } if ( this . _globalState . hemisphericLight ) { new HemisphericLight ( "Hemispheric light" , new Vector3 ( 0 , 1 , 0 ) , this . _scene ) ; } if ( this . _globalState . directionalLight0 ) { let dir0 = new DirectionalLight ( "Directional light #0" , new Vector3 ( 0.841626576496605 , - 0.2193391004130599 , - 0.49351298337996535 ) , this . _scene ) ; dir0 . intensity = 0.9 ; dir0 . diffuse = new Color3 ( 0.9294117647058824 , 0.9725490196078431 , 0.996078431372549 ) ; dir0 . specular = new Color3 ( 0.9294117647058824 , 0.9725490196078431 , 0.996078431372549 ) ; dir0 . parent = this . _lightParent ; } if ( this . _globalState . directionalLight1 ) { let dir1 = new DirectionalLight ( "Directional light #1" , new Vector3 ( - 0.9519937437504213 , - 0.24389315636999764 , - 0.1849974057546125 ) , this . _scene ) ; dir1 . intensity = 1.2 ; dir1 . specular = new Color3 ( 0.9803921568627451 , 0.9529411764705882 , 0.7725490196078432 ) ; dir1 . diffuse = new Color3 ( 0.9803921568627451 , 0.9529411764705882 , 0.7725490196078432 ) ; dir1 . parent = this . _lightParent ; } } private _prepareScene ( ) { this . _camera . useFramingBehavior = this . _globalState . mode === NodeMaterialModes . Material ; switch ( this . _globalState . mode ) { case NodeMaterialModes . Material : { this . _prepareLights ( ) ; var framingBehavior = this . _camera . getBehaviorByName ( "Framing" ) as FramingBehavior ; setTimeout ( ( ) => { framingBehavior . framingTime = 0 ; framingBehavior . elevationReturnTime = - 1 ; if ( this . _scene . meshes . length ) { var worldExtends = this . _scene . getWorldExtends ( ) ; this . _camera . lowerRadiusLimit = null ; this . _camera . upperRadiusLimit = null ; framingBehavior . zoomOnBoundingInfo ( worldExtends . min , worldExtends . max ) ; } this . _camera . pinchPrecision = 200 / this . _camera . radius ; this . _camera . upperRadiusLimit = 5 * this . _camera . radius ; } ) ; this . _camera . wheelDeltaPercentage = 0.01 ; this . _camera . pinchDeltaPercentage = 0.01 ; this . _handleAnimations ( ) ; break ; } case NodeMaterialModes . PostProcess : case NodeMaterialModes . ProceduralTexture : { this . _camera . radius = 4 ; this . _camera . upperRadiusLimit = 10 ; break ; } case NodeMaterialModes . Particle : { this . _camera . radius = this . _globalState . previewType === PreviewType . Explosion ? 50 : this . _globalState . previewType === PreviewType . DefaultParticleSystem ? 6 : 20 ; this . _camera . upperRadiusLimit = 5000 ; this . _globalState . particleSystemBlendMode = this . _particleSystem ?. blendMode ?? ParticleSystem . BLENDMODE_STANDARD ; break ; } } let serializationObject = this . _nodeMaterial . serialize ( ) ; this . _updatePreview ( serializationObject ) ; } private _refreshPreviewMesh ( ) { if ( this . _currentType !== this . _globalState . previewType || this . _currentType === PreviewType . Custom ) { this . _currentType = this . _globalState . previewType ; if ( this . _meshes && this . _meshes . length ) { for ( var mesh of this . _meshes ) { mesh . dispose ( ) ; } } this . _meshes = [ ] ; if ( this . _layer ) { this . _layer . dispose ( ) ; this . _layer = null ; } let lights = this . _scene . lights . slice ( 0 ) ; for ( var light of lights ) { light . dispose ( ) ; } this . _engine . releaseEffects ( ) ; if ( this . _particleSystem ) { this . _particleSystem . onBeforeDrawParticlesObservable . clear ( ) ; this . _particleSystem . onDisposeObservable . clear ( ) ; this . _particleSystem . stop ( ) ; this . _particleSystem . dispose ( ) ; this . _particleSystem = null ; } SceneLoader . ShowLoadingScreen = false ; this . _globalState . onIsLoadingChanged . notifyObservers ( true ) ; if ( this . _globalState . mode === NodeMaterialModes . Material ) { switch ( this . _globalState . previewType ) { case PreviewType . Box : SceneLoader . AppendAsync ( "https://models.babylonjs.com/" , "roundedCube.glb" , this . _scene ) . then ( ( ) => { this . _meshes . push ( ... this . _scene . meshes ) ; this . _prepareScene ( ) ; } ) ; return ; case PreviewType . Sphere : this . _meshes . push ( CreateSphere ( "dummy-sphere" , { segments : 32 , diameter : 2 } , this . _scene ) ) ; break ; case PreviewType . Torus : this . _meshes . push ( CreateTorus ( "dummy-torus" , { diameter : 2 , thickness : 0.5 , tessellation : 32 } , this . _scene ) ) ; break ; case PreviewType . Cylinder : SceneLoader . AppendAsync ( "https://models.babylonjs.com/" , "roundedCylinder.glb" , this . _scene ) . then ( ( ) => { this . _meshes . push ( ... this . _scene . meshes ) ; this . _prepareScene ( ) ; } ) ; return ; case PreviewType . Plane : let plane = CreateGround ( "dummy-plane" , { width : 2 , height : 2 , subdivisions : 128 } , this . _scene ) ; plane . scaling . y = - 1 ; plane . rotation . x = Math . PI ; this . _meshes . push ( plane ) ; break ; case PreviewType . ShaderBall : SceneLoader . AppendAsync ( "https://models.babylonjs.com/" , "shaderBall.glb" , this . _scene ) . then ( ( ) => { this . _meshes . push ( ... this . _scene . meshes ) ; this . _prepareScene ( ) ; } ) ; return ; case PreviewType . Custom : SceneLoader . AppendAsync ( "file:" , this . _globalState . previewFile , this . _scene ) . then ( ( ) => { this . _meshes . push ( ... this . _scene . meshes ) ; this . _prepareScene ( ) ; } ) ; return ; } } else if ( this . _globalState . mode === NodeMaterialModes . ProceduralTexture ) { this . _layer = new Layer ( "proceduralLayer" , null , this . _scene ) ; } else if ( this . _globalState . mode === NodeMaterialModes . Particle ) { switch ( this . _globalState . previewType ) { case PreviewType . DefaultParticleSystem : this . _particleSystem = ParticleHelper . CreateDefault ( new Vector3 ( 0 , 0 , 0 ) , 500 , this . _scene ) ; this . _particleSystem . blendMode = DataStorage . ReadNumber ( "DefaultParticleSystemBlendMode" , ParticleSystem . BLENDMODE_ONEONE ) ; this . _particleSystem . start ( ) ; break ; case PreviewType . Bubbles : this . _particleSystem = new ParticleSystem ( "particles" , 4000 , this . _scene ) ; this . _particleSystem . particleTexture = new Texture ( "https://assets.babylonjs.com/particles/textures/explosion/Flare.png" , this . _scene ) ; this . _particleSystem . minSize = 0.1 ; this . _particleSystem . maxSize = 1.0 ; this . _particleSystem . minLifeTime = 0.5 ; this . _particleSystem . maxLifeTime = 5.0 ; this . _particleSystem . minEmitPower = 0.5 ; this . _particleSystem . maxEmitPower = 3.0 ; this . _particleSystem . createBoxEmitter ( new Vector3 ( - 1 , 1 , - 1 ) , new Vector3 ( 1 , 1 , 1 ) , new Vector3 ( - 0.1 , - 0.1 , - 0.1 ) , new Vector3 ( 0.1 , 0.1 , 0.1 ) ) ; this . _particleSystem . emitRate = 100 ; this . _particleSystem . blendMode = DataStorage . ReadNumber ( "DefaultParticleSystemBlendMode" , ParticleSystem . BLENDMODE_ONEONE ) ; this . _particleSystem . color1 = new Color4 ( 1 , 1 , 0 , 1 ) ; this . _particleSystem . color2 = new Color4 ( 1 , 0.5 , 0 , 1 ) ; this . _particleSystem . gravity = new Vector3 ( 0 , - 1.0 , 0 ) ; this . _particleSystem . start ( ) ; break ; case PreviewType . Explosion : this . _loadParticleSystem ( this . _globalState . previewType , 1 ) ; return ; case PreviewType . Fire : case PreviewType . Rain : case PreviewType . Smoke : this . _loadParticleSystem ( this . _globalState . previewType ) ; return ; case PreviewType . Custom : ReadFile ( this . _globalState . previewFile , ( json ) => { this . _particleSystem = ParticleSystem . Parse ( JSON . parse ( json ) , this . _scene , "" ) ; this . _particleSystem . start ( ) ; this . _prepareScene ( ) ; } , undefined , false , ( error ) => { console . log ( error ) ; } ) ; return ; } } this . _prepareScene ( ) ; } } private _loadParticleSystem ( particleNumber , systemIndex = 0 , prepareScene = true ) { let name = "" ; switch ( particleNumber ) { case PreviewType . Explosion : name = "explosion" ; break ; case PreviewType . Fire : name = "fire" ; break ; case PreviewType . Rain : name = "rain" ; break ; case PreviewType . Smoke : name = "smoke" ; break ; } ParticleHelper . CreateAsync ( name , this . _scene ) . then ( ( set ) => { for ( let i = 0 ; i < set . systems . length ; ++ i ) { if ( i == systemIndex ) { this . _particleSystem = set . systems [ i ] ; this . _particleSystem . disposeOnStop = true ; this . _particleSystem . onDisposeObservable . add ( ( ) => { this . _loadParticleSystem ( particleNumber , systemIndex , false ) ; } ) ; this . _particleSystem . start ( ) ; } else { set . systems [ i ] . dispose ( ) ; } } if ( prepareScene ) { this . _prepareScene ( ) ; } else { let serializationObject = this . _nodeMaterial . serialize ( ) ; this . _updatePreview ( serializationObject ) ; } } ) ; } private _forceCompilationAsync ( material , mesh ) { return material . forceCompilationAsync ( mesh ) ; } private _updatePreview ( serializationObject ) { try { let store = NodeMaterial . IgnoreTexturesAtLoadTime ; NodeMaterial . IgnoreTexturesAtLoadTime = false ; let tempMaterial = NodeMaterial . Parse ( serializationObject , this . _scene ) ; NodeMaterial . IgnoreTexturesAtLoadTime = store ; tempMaterial . backFaceCulling = this . _globalState . backFaceCulling ; tempMaterial . needDepthPrePass = this . _globalState . depthPrePass ; if ( this . _postprocess ) { this . _postprocess . dispose ( this . _camera ) ; this . _postprocess = null ; } if ( this . _proceduralTexture ) { this . _proceduralTexture . dispose ( ) ; this . _proceduralTexture = null ; } switch ( this . _globalState . mode ) { case NodeMaterialModes . PostProcess : { this . _globalState . onIsLoadingChanged . notifyObservers ( false ) ; this . _postprocess = tempMaterial . createPostProcess ( this . _camera , 1.0 , Constants . TEXTURE_NEAREST_SAMPLINGMODE , this . _engine ) ; const currentScreen = tempMaterial . getBlockByPredicate ( ( block ) => block instanceof CurrentScreenBlock ) ; if ( currentScreen && this . _postprocess ) { this . _postprocess . externalTextureSamplerBinding = true ; this . _postprocess . onApplyObservable . add ( ( effect ) => { effect . setTexture ( "textureSampler" , ( currentScreen as CurrentScreenBlock ) . texture ) ; } ) ; } if ( this . _material ) { this . _material . dispose ( false , true ) ; } this . _material = tempMaterial ; break ; } case NodeMaterialModes . ProceduralTexture : { this . _globalState . onIsLoadingChanged . notifyObservers ( false ) ; this . _proceduralTexture = tempMaterial . createProceduralTexture ( 512 , this . _scene ) ; if ( this . _material ) { this . _material . dispose ( false , true ) ; } if ( this . _layer ) { this . _layer . texture = this . _proceduralTexture ; } break ; } case NodeMaterialModes . Particle : { this . _globalState . onIsLoadingChanged . notifyObservers ( false ) ; this . _particleSystem ! . onBeforeDrawParticlesObservable . clear ( ) ; this . _particleSystem ! . onBeforeDrawParticlesObservable . add ( ( effect ) => { const textureBlock = tempMaterial . getBlockByPredicate ( ( block ) => block instanceof ParticleTextureBlock ) ; if ( textureBlock && ( textureBlock as ParticleTextureBlock ) . texture && effect ) { effect . setTexture ( "diffuseSampler" , ( textureBlock as ParticleTextureBlock ) . texture ) ; } } ) ; tempMaterial . createEffectForParticles ( this . _particleSystem ! ) ; this . _particleSystem ! . blendMode = this . _globalState . particleSystemBlendMode ; if ( this . _material ) { this . _material . dispose ( false , true ) ; } this . _material = tempMaterial ; break ; } default : { if ( this . _meshes . length ) { let tasks = this . _meshes . map ( ( m ) => this . _forceCompilationAsync ( tempMaterial , m ) ) ; Promise . all ( tasks ) . then ( ( ) => { for ( var mesh of this . _meshes ) { mesh . material = tempMaterial ; } if ( this . _material ) { this . _material . dispose ( false , true ) ; } this . _material = tempMaterial ; this . _globalState . onIsLoadingChanged . notifyObservers ( false ) ; } ) . catch ( ( reason ) => { this . _globalState . onLogRequiredObservable . notifyObservers ( new LogEntry ( "Shader compilation error:\r\n" + reason , true ) ) ; this . _globalState . onIsLoadingChanged . notifyObservers ( false ) ; } ) ; } else { this . _material = tempMaterial ; } break ; } } } catch ( err ) { this . _globalState . onIsLoadingChanged . notifyObservers ( false ) ; } } public dispose ( ) { this . _nodeMaterial . onBuildObservable . remove ( this . _onBuildObserver ) ; this . _globalState . onPreviewCommandActivated . remove ( this . _onPreviewCommandActivatedObserver ) ; this . _globalState . onUpdateRequiredObservable . remove ( this . _onUpdateRequiredObserver ) ; this . _globalState . onAnimationCommandActivated . remove ( this . _onAnimationCommandActivatedObserver ) ; this . _globalState . onPreviewBackgroundChanged . remove ( this . _onPreviewBackgroundChangedObserver ) ; this . _globalState . onBackFaceCullingChanged . remove ( this . _onBackFaceCullingChangedObserver ) ; this . _globalState . onDepthPrePassChanged . remove ( this . _onDepthPrePassChangedObserver ) ; this . _globalState . onLightUpdated . remove ( this . _onLightUpdatedObserver ) ; if ( this . _material ) { this . _material . dispose ( false , true ) ; } this . _camera . dispose ( ) ; for ( var mesh of this . _meshes ) { mesh . dispose ( ) ; } this . _scene . dispose ( ) ; this . _engine . dispose ( ) ; } }