pxlNav/Environment.js

// -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
// --   The Core pxlNav Engine File          --
// --    -- -- -- -- -- -- -- --             --
// --  Written by Kevin Edzenga; 2020-2024   --
// --    Using js as its backbone      --
// --                                        --
// --  The 'Environment' class manages       --
// --    engine management & render stack    --
// --  This is the class that interprets     --
// --    the rooms found in -                --
// --     ./Source/js/pxlRooms               --
// --  To make a custom room,                --
// --    See the 'templateRoom' project      --
// --                                        --
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- --




import {
  Vector2,
  Vector3,
  WebGLRenderTarget,
  RGBAFormat,
  LinearFilter,
  DepthTexture,
  DepthFormat,
  UnsignedShortType,
  FloatType,
  HalfFloatType,
  Scene,
  Group,
  Color,
  FogExp2,
  ShaderMaterial,
  FrontSide,
  LinearSRGBColorSpace
} from "../libs/three/three.module.min.js";

import { ANTI_ALIASING, VERBOSE_LEVEL, COLLIDER_TYPE } from "./core/Enums.js";
import { pxlOptions } from "./core/Options.js";

import { EffectComposer } from '../libs/three/EffectComposer.js';
import { RenderPass } from '../libs/three/RenderPass.js';
import { ShaderPass } from '../libs/three/ShaderPass.js';
import { CopyShader } from '../libs/three/CopyShader.js';
// TODO : Remove all traces of bloom passes, implement Neurous Box Blur passes
import { UnrealBloomPass } from '../libs/three/UnrealBloomPass.js';


// TODO : This class needs breaking up into BaseEnvironment & MainEnvironment expand

/**
 * @namespace pxlEnv
 * @description pxlNav Environment
 */
export class Environment{
  constructor( options, mainRoom='Default', mobile ){
    this.engine=null;
    this.scene=null;
    this.parentGroupList={};
    this.parentGroupList[mainRoom]=[];
    this.parentNameList=[];
    this.pxlOptions=options;



    // -- -- -- --
    // TODO : Move to pxlQuality, when I finally get to that module
    this.prevRenderMS=0;
    this.nextRenderMS=0;
    // Max frame rate - 
    this.fps = 30;
    if( this.pxlOptions.fps.hasOwnProperty("mobile") ){
      this.fps = mobile ? this.pxlOptions.fps.mobile : this.pxlOptions.fps.pc;
    }else if( this.pxlOptions.fps.hasOwnProperty("Mobile") ){ // Legacy
      this.fps = mobile ? this.pxlOptions.fps.Mobile : this.pxlOptions.fps.PC;
    }
    this.renderInterval = 1.0 / this.fps;
    // -- -- -- --

    let pxlRoomName = "Default";
    if( this.pxlOptions.hasOwnProperty("pxlRoomName") ){
      pxlRoomName = this.pxlOptions.pxlRoomName;
    }else{
      pxlRoomName = mainRoom || "Default";
    }

    this.pxlRoomAbsRoot = pxlRoomName;
    let splitRoot = pxlRoomName.split("/");
    splitRoot.splice(0,1);
    splitRoot = splitRoot.join("/");
    pxlRoomName = pxlRoomName.startsWith('./') ? pxlRoomName.slice(1) : pxlRoomName;
    
    if( this.pxlOptions.hasOwnProperty("pxlRoomRoot") ){
      this.pxlRoomLclRoot = this.pxlOptions.pxlRoomRoot;
    }else{
      this.pxlRoomLclRoot = "./"+pxlRoomName.split("/").pop().join("/");
    }
    
    this.mainRoom=mainRoom; // Main environment room
    this.bootRoom=mainRoom; // Room to start pxlNav in
    this.bootLocation=null; // Camera Position to start pxlNav at
    this.currentRoom=mainRoom; // Current residing room
    this.roomNameList=[mainRoom]; // Room name list
    this.roomSubList={};
    this.roomSceneList={}; // Room Object list; [ Environment Object, ... ]
    this.roomSceneList[mainRoom]=this;
    this.roomPostGuiCalls=[];
    this.jmaCheckConnection=false;
        
    this.checkContext=0;
    this.activeContext=false;
    
    this.warpPortalTextures={};
    this.warpZoneRenderTarget=null;
    this.currentAudioZone=0;
    
    this.pxlUtils=null;
    this.pxlEnums=null;
    this.pxlTimer=null;
    this.pxlAnim=null;
    this.pxlColliders=null;
    this.pxlAutoCam=null;
    this.pxlAudio=null;
    this.pxlFile=null;
    this.pxlVideo=null;
    this.pxlQuality=null;
    this.pxlUser=null;
    this.pxlShaders=null;
    this.pxlDevice=null;
    this.pxlCamera=null;
    this.pxlGuiDraws=null;
    
    
    this.cloud3dTexture=null;
    this.softNoiseTexture=null;
    this.detailNoiseTexture=null;
    this.chroAberUVTexture=null;
    this.chroAberUVAlpha=null;
    this.blockAnimTexture=null;
    this.userScreenIntensity=new Vector2(0,.8); // User Screen Multi, x=Base Color Mult, y=Added Boost
    this.portaluserScreenIntensity=new Vector2(1,0);
    this.portalMtlRate=.7;
    this.mobile=mobile;
    
    this.camNear=.1;
    this.camFar=5000;
    
    this.fogMult = new Vector2(0,0);
    this.fogColor=new Color(.07,.07,.10);//new Color(.07,.07,.10);
    this.fogColorSky=new Color(.1,.1,.12);
    this.fogExp=.0003;
    this.fog=new FogExp2( this.fogColor, this.fogExp);

    this.listener=null;
    this.posted=false;
    this.postIntro=false;
    
    this.camLocation = {};
    this.camInitPos=new Vector3(0,15,0);
    this.camInitLookAt=new Vector3(0,15,0);
    this.camThumbPos=new Vector3(0,0,0);
    this.camThumbLookAt=new Vector3(0,20,0);
    this.camReturnPos=new Vector3(0,0,0);
    this.camReturnLookAt=new Vector3(0,0,0);
    this.camLobbyPos=new Vector3(0, 15, 0);
    this.camLobbyLookAt=new Vector3(0, 15, -100);
    this.pxlCamFOV={ 'PC':60, 'MOBILE':80 };
    this.pxlCamZoom=1;
    this.pxlCamAspect=1;
    this.pxlCamNearClipping = this.camNear;
    this.pxlCamFarClipping = this.camFar;
    
    this.groupList=[];
    this.geoList=[];
    this.geoLoadList=[]; // 0 Not loaded, 1 Loaded, 2 Loaded and Processed (Setting Dependants)
    this.geoLoadListComplete=0;
    this.lightList=[];
    this.returnPortalGlowList=[];
    this.roomWarpVisuals={};
    this.proximityGeo=null;
    this.userAvatarGroup=new Group();
    
    // ## Warp visuals to dict
    this.warpVisualActive=false;
    this.warpVisualMaxTime=[1.5,1];
    this.warpVisualStartTime=0;
    this.warpVisualFader=new Vector2(0,1);
    this.warpVisualCmd=null;

    // ## Move passes to a dict
    this.stepShaderFuncArr=[];
    this.mapMotionBlurPass=null;
    this.mapCopyMotionBlurPass=null;
    this.mapOverlayHeavyPass=null;
    this.mapOverlayPass=null;
    this.mapOverlaySlimPass=null;
    this.mapBoxAAPass=null;
    this.mapCrossAAPass=null;
    this.mapWorldPosMaterial=null;
    this.mapGlowPass=null;
    this.mapGlowMotionBlur=null;
    this.mapComposer=null;
    this.mapComposerMotionBlur=null;
    this.mapComposerBloom=null;
    this.mapComposerGlow=null;
    this.chromaticAberrationPass=null;
    this.chroAberrationRoomPass=null;
    this.lizardKingPass=null;
    this.lizardKingRoomPass=null;
    this.mapComposerWarpPass=null;
    this.blurScreenMerge=null;
    this.pxlRenderSettings={
      'exposure':1.0,
      'mult':( mobile ? 1.0 : 1.0 ),
      'bloomStrength':0.5,
      'bloomThresh':.6,
      'bloomRadius':.05,
    }
    this.exposureShiftActive=false;

    this.delayComposer=null;
    this.delayPass=null;
    
    this.chroAberMult=new Vector2(1,0);
    this.blurDirCur=new Vector2(0,0);
    this.blurDirPrev=new Vector2(0,0);
    
    this.shaderPasses={};
    
    this.roomComposer=null;
    this.roomRenderPass=null;
    this.roomBloomPass=null;
    this.roomGlowPass=null;
    
    this.blurComposer=null;
    this.glowPassMask = new Vector2(1.0,0.0);
    
    this.objectClickable=[];
    this.objectClickableObjectList={};
    this.clickablePrevActiveObject=null;
        
    this.promoClickable=[];
    this.promoClickableObjectList={};
    this.promoPrevActiveObject=null;
    this.promoClickableLinks={};

    // Test Web Cam
    this.remoteVideoTextureList=[];
    this.remoteUserNameList=[];
    this.remoteUserMediaList={};
    this.remoteUserVideoList=[];
    this.remoteUserAudioList=[];
    this.camScreenData={
      count:0,
      prevCount:0,
      checkUserCount:true,
      checkVideoStatus:true,
      findCamCount:()=>{},
      videoObjectList:[],
      screenGeoList:[],
      screenClickable:[],
      screenMtlUniforms:[],
      userDataList:[],
      camFindInfoList:[]
    };
    
    this.curUserCount=0;
    this.prevUserCount=0;

    this.emit=(type,value)=>{};
    
  }
  
  setDependencies( pxlNav ){
    this.scene=pxlNav.scene;
    this.pxlUtils=pxlNav.pxlUtils;
    this.pxlEnums=pxlNav.pxlEnums;
    this.pxlTimer=pxlNav.pxlTimer;
    this.pxlAnim=pxlNav.pxlAnim;
    this.pxlColliders=pxlNav.pxlColliders;
    this.pxlAutoCam=pxlNav.pxlAutoCam;
    this.pxlAudio=pxlNav.pxlAudio;
    this.pxlFile=pxlNav.pxlFile;
    this.pxlVideo=pxlNav.pxlVideo;
    this.pxlQuality=pxlNav.pxlQuality;
    this.pxlUser=pxlNav.pxlUser;
    this.pxlShaders=pxlNav.pxlShaders;
    this.pxlDevice=pxlNav.pxlDevice;
    this.pxlCamera=pxlNav.pxlCamera;
    this.pxlGuiDraws=pxlNav.pxlGuiDraws;
    this.emit=pxlNav.emit.bind(pxlNav);
  }
  
  log( msg, level=VERBOSE_LEVEL.INFO ){
    if( this.pxlOptions.verbose > VERBOSE_LEVEL.INFO ){
      console.log( msg );
    }
  }

  // Function required
  init(){

    let optionKeys=Object.keys( this.pxlOptions );
    let defaultKeys=Object.keys( pxlOptions );
    defaultKeys.forEach( (k)=>{
      if( !optionKeys.includes( k ) ){
        this.pxlOptions[k]=pxlOptions[k];
      }
    });

    //this.setExposure( 0 );
    let subList=Object.keys( this.roomSubList );
    subList.forEach( (s)=>{
        this.roomSubList[ s ].init();
    });
  }
    
  boot(){
    //this.pxlQuality.attachModule( this );
  }
    
  setBootRoom(bootRoom, bootLocation){
      this.bootRoom=bootRoom;
      this.bootLocation=bootLocation;
  }
    
  postBoot(){
		
		this.pxlGuiDraws.togglePageDisplay();

    this.roomSceneList[this.bootRoom].start();
    
    this.posted=true;

    //this.buildSnow();

    // Trigger Mobile or PC How-To 
    if( this.pxlOptions.showOnboarding ){
      if( this.pxlDevice.mobile || this.pxlAutoCam.enabled){
        this.pxlGuiDraws.toggleMobileWelcome(true);
      }else{
        this.pxlGuiDraws.iconEvent( "click", this.pxlGuiDraws.hudIcons.helpIcon, "help" );
      }
    }
  }
    
  postHelpIntro(){
    // If the device is a computer, without autocam, send player details to the server
    //   TODO : This will need to be accessible from the room object and set-up networking as an extention
    if( !this.pxlDevice.mobile && !this.pxlAutoCam.enabled ){
      this.pxlCamera.jogServerMemory();
    
    // If the device is a mobile, or autocam is enabled,
    //   Trigger a valid collider to allow the camera to be placed.
    }else{
      this.pxlCamera.colliderValid=true;
      this.pxlCamera.eventCheckStatus=true;
      this.pxlCamera.colliderShiftActive=true;
      this.pxlCamera.nearestFloorObjName="mobile";
      this.pxlCamera.colliderCurObjHit="AudioTrigger_2";
      this.pxlCamera.proximityScaleTrigger=true;
      this.exposureShiftActive=true;
      
      // TODO : Media (audio, video, music) needs to be an setting on the pxlOptions object
      if( !this.pxlDevice.mobile ){
        this.pxlAudio.play();
        setTimeout( ()=>{
          this.pxlAudio.initPlay();
        },100);
      }
    }
    this.postIntro=true;
  }

  start(){
    this.init();
  }
    
  step(){
        
    // ## Should just have a stepper system set up...
    //      Easier for modular installations
    this.pxlTimer.step();
    this.pxlAudio.step();
    this.pxlQuality.step();
    this.pxlDevice.step();
    if( this.pxlAutoCam.step() ){
        this.pxlCamera.step();
    }
    this.pxlGuiDraws.step();

    this.stepWarpPass();
        
    if( this.pxlTimer.msRunner.x > this.checkContext && this.activeContext ){
      this.checkContext=this.pxlTimer.msRunner.x+1;
      let tmpCanvas=document.createElement('canvas');
      try{
        let ctxVal=!!tmpCanvas.getContext('webgl');
      }catch(err){
        this.activeContext=true;
        this.pxlGuiDraws.pxlNavCanvas.style.display='none';
      }
    }
    
    //if( this.pxlDevice.mobile && this.exposureShiftActive ){
      //this.pxlCamera.colliderShiftActive=!(this.pxlCamera.colliderAdjustPerc==1 || this.pxlCamera.colliderAdjustPerc==0);
      //this.updateCompUniforms(curExp);
    //}
  }
  
  // Function required, stoping functions
  async stop(){
    this.setExposure( 0 );
    let subList=Object.keys( this.roomSubList );
    subList.forEach( (s)=>{
      this.roomSubList[ s ].stop();
    });
  }
  
  loadRoom(roomName){
    return new Promise( (resolve, reject) =>{

      this.log("Loading Room - ", roomName);
      
      let curImportPath=`${this.pxlRoomLclRoot}/${roomName}/${roomName}.js`;
      
      import( curImportPath )
        .then((module)=>{
          let roomObj=new module[roomName]( roomName, `${this.pxlRoomLclRoot}/${roomName}/`, this.pxlTimer.msRunner, null, null, this.cloud3dTexture);
          roomObj.setDependencies( this );

          roomObj.camera=this.pxlCamera.camera;
          roomObj.scene=new Scene();
          if( !roomObj.userAvatarGroup ){
              roomObj.userAvatarGroup=new Group();
          }
          roomObj.scene.add( roomObj.userAvatarGroup );
          
          var options = {
              format : RGBAFormat,
              antialias: false,
              sortObjects:true,
              alpha:true,
              type : /(iPad|iPhone|iPod)/g.test(navigator.userAgent) ? HalfFloatType : FloatType
          };
          
          roomObj.scene.renderTarget=new WebGLRenderTarget( this.pxlDevice.sW*this.pxlQuality.screenResPerc, this.pxlDevice.sH*this.pxlQuality.screenResPerc,options);
          roomObj.scene.renderTarget.texture.format=RGBAFormat;
          roomObj.scene.renderTarget.texture.minFilter=LinearFilter;
          roomObj.scene.renderTarget.texture.magFilter=LinearFilter;
          roomObj.scene.renderTarget.texture.generateMipmaps=false;
          //roomObj.scene.renderTarget.texture.type=FloatType;
          roomObj.scene.renderTarget.depthBuffer=true;
          roomObj.scene.renderTarget.depthTexture = new DepthTexture( this.pxlDevice.sW*this.pxlQuality.screenResPerc, this.pxlDevice.sH*this.pxlQuality.screenResPerc );
          roomObj.scene.renderTarget.depthTexture.format=DepthFormat;
          //roomObj.scene.renderTarget.depthTexture.type=UnsignedIntType;
          roomObj.scene.renderTarget.depthTexture.type=UnsignedShortType;
          
          // World Pos Target
          //   Remains from a Portal Room Snapshot Display system
          //     Would be desired tech, but needs re-implementation
          // roomObj.scene.renderWorldPos=new WebGLRenderTarget( this.pxlDevice.sW*this.pxlQuality.screenResPerc, this.pxlDevice.sH*this.pxlQuality.screenResPerc,options);
          // roomObj.scene.renderWorldPos.texture.format=RGBAFormat;
          // roomObj.scene.renderWorldPos.texture.minFilter=NearestFilter;
          // roomObj.scene.renderWorldPos.texture.magFilter=NearestFilter;
          // roomObj.scene.renderWorldPos.texture.generateMipmaps=false;
          
          roomObj.cloud3dTexture=this.cloud3dTexture;
          if( !this.roomNameList.includes( roomName ) ){
            this.roomNameList.push( roomName );
          }
          this.roomSceneList[ roomName ]=roomObj;
      
          resolve(true);
        })
        .catch((err)=>{
          if(this.pxlOptions.verbose >= VERBOSE_LEVEL.ERROR){
            console.log(err);
          }
          reject(err);
        });
    });
  }
  
  startAllRooms(){
    this.roomNameList.forEach( (roomName)=>{
        this.startRoom( this.roomSceneList[ roomName ] );
    });
  }
  
  startRoom( room ){
    room.active=false;
    room.startTime=this.pxlTimer.msRunner.x;
  }
    
  newSubRoom( roomObject ){
    this.roomSubList[ roomObject.getName() ]=roomObject;
  }
  addToRooms( obj ){
    let roomObjDict={};
    
    this.roomNameList.forEach( (n)=>{
      let dupe=obj.clone();
      this.roomSceneList[ n ].scene.add( dupe );
      roomObjDict[n]=dupe;
    });
    return roomObjDict;
  }
  
  addToRoomLayers( obj, layer=1 ){
    let roomObjDict={};
    
    this.roomNameList.forEach( (n)=>{
      let dupe=obj.clone();
      this.roomSceneList[ n ].scene.add( dupe );
      roomObjDict[n]=dupe;
      dupe.layers.set( layer );
    });
    return roomObjDict;
  }
  
  addToRoomParents( obj, parent ){
    let roomObjDict={};
    
    if( !this.parentNameList.includes( parent ) ){
      this.parentNameList.push( parent );
    }
    
    this.roomNameList.forEach( (n)=>{
      let dupe=obj.clone();
      if( !this.parentGroupList[ n ] ){
        this.parentGroupList[ n ]={};
      }
      if( !this.parentGroupList[ n ][ parent ] ){
        let newGroup = new Group();
        this.parentGroupList[ n ][ parent ]=newGroup;
        this.roomSceneList[ n ].scene.add( newGroup );
      }
      this.parentGroupList[ n ][ parent ].add( dupe );
      roomObjDict[n]=dupe;
    });
    return roomObjDict;
  }
  
  // Initially, main room was loaded as a nexus room
  //   Should see if this is still necessary
  buildRoomEnvironments(){
    
    this.log("Building Room Environments");
    this.log(this.roomNameList);
    let loadPromisList=[];
    
    this.roomNameList.forEach( (r)=>{
      this.roomSceneList[ r ].init();
      if( this.roomSceneList[ r ].build ){
        this.roomSceneList[ r ].build();
      }
    });
  }
  
  getArtistInfo(){
    return null;
  }
    
  setFogHue( orig=[0,0], rot=[1,1] ){
    let hsl=this.fog.color.getHSL();

    let atanVals=[ rot[0]-orig[0], rot[1]-orig[1] ];
    //let scalar=(atanVals[0]+atanVals[1]);
    //atanVals= [ atanVals[0]/scalar, atanVals[0]/scalar ]
    let hRot=Math.abs(Math.atan2( atanVals[0], atanVals[1] ) * 0.3183098861837907 ); // 1/pi
    
    let scale= (atanVals[0]**2+atanVals[1]**2)**.5 / Math.max(this.pxlDevice.sW, this.pxlDevice.sH);
    this.fog.color.setHSL( hRot, scale*.5+.3, scale*.5 );
    
    if( this.roomSceneList[this.currentRoom] && this.roomSceneList[this.currentRoom].setFog ){
      this.roomSceneList[this.currentRoom].setFog( this.fog.color );
    }
  }
    
  //%=
  // Return Primary Shader Material
  readShader( objShader="" ){
    if( objShader=="script_fog" ){
      this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
        
      if(this.mapOverlayHeavyPass.enabled==true){
        return this.mapOverlayHeavyPass.material ;
      }else if(this.mapOverlayPass.enabled==true){
        return this.mapOverlayPass.material ;
      }else if(this.mapOverlaySlimPass.enabled==true){
        return this.mapOverlaySlimPass.material ;
      }
    }else if( objShader=="script_dArrows" ){
      this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
      return this.geoList[ "dArrows" ][0].material;
    }else if( objShader=="script_userScreens" ){
      this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
      return this.camScreenData.screenGeoList[0].material;
    }else if( objShader=="script_warpZonePortals" ){
      this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
      return this.returnPortalGlowList[0].material;
        
    }else if( objShader=="script_lizardking" ){
      this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
      return this.lizardKingPass.material;
    }else if( objShader=="script_majorTom" ){
      this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
      return this.pxlUser.starFieldPass.material;
    }else if( objShader=="script_fractalSubstrate" ){
      this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
      return this.pxlUser.crystallinePass.material;
    }else if( objShader=="script_fractalEcho" ){
      this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
      return this.delayPass.material;
        
    }else{
      let geoRead=objShader.split("_");
      geoRead.shift();
      geoRead=geoRead.join("_");
      if( this.geoList[ geoRead ] ){
          this.pxlGuiDraws.guiWindows["shaderGui"].currentShader=objShader;
          return this.geoList[ geoRead ].material ;
      }
    }
        
        
    //return this.pxlUser.starFieldPass.material;
  }
  setShader( unis, vert, frag ){
        let setShaderMtl;
        
        let objShader=this.pxlGuiDraws.guiWindows["shaderGui"].currentShader;
        if( objShader=="script_fog" ){
            if(this.mapOverlayHeavyPass.enabled==true){
                setShaderMtl= this.mapOverlayHeavyPass.material ;
            }else if(this.mapOverlayPass.enabled==true){
                setShaderMtl= this.mapOverlayPass.material ;
            }else if(this.mapOverlaySlimPass.enabled==true){
                setShaderMtl= this.mapOverlaySlimPass.material ;
            }
        }else if( objShader=="script_dArrows" ){
            this.geoList[ "dArrows" ].forEach( (o)=>{
                setShaderMtl=o.material;
                setShaderMtl.vertexShader=vert;
                setShaderMtl.fragmentShader=frag;
                setShaderMtl.needsUpdate=true;
            });
            return;
        }else if( objShader=="script_userScreens" ){
            this.camScreenData.screenGeoList.forEach( (o)=>{
                setShaderMtl=o.material;
                setShaderMtl.vertexShader=vert;
                setShaderMtl.fragmentShader=frag;
                setShaderMtl.needsUpdate=true;
            });
            return;
        }else if( objShader=="script_warpZonePortals" ){
            this.returnPortalGlowList.forEach( (o)=>{
                setShaderMtl=o.material;
                setShaderMtl.vertexShader=vert;
                setShaderMtl.fragmentShader=frag;
                setShaderMtl.needsUpdate=true;
            });
            return;
            
        }else if( objShader=="script_lizardking" ){
                setShaderMtl=this.lizardKingPass.material;
        }else if( objShader=="script_majorTom" ){
                setShaderMtl=this.pxlUser.starFieldPass.material;
        }else if( objShader=="script_fractalSubstrate" ){
                setShaderMtl=this.pxlUser.crystallinePass.material;
        }else if( objShader=="script_fractalEcho" ){
                setShaderMtl=this.delayPass.material;
            
        }else{
            let geoRead=objShader.split("_");
            geoRead.shift();
            geoRead=geoRead.join("_");
            if( this.geoList[ geoRead ] ){
                setShaderMtl= this.geoList[ geoRead ].material ;
            }
        }
        
        if(setShaderMtl){
            setShaderMtl.vertexShader=vert;
            setShaderMtl.fragmentShader=frag;
            setShaderMtl.needsUpdate=true;
        }
  }
  //%
  
  // Load the given texture file from the internal pxlNav 'assetRoot'
  //   Default is "./assets/" from the web root.
  //     Please pass the path to your asset folder when creating your pxlNav object.
  // It would be best if you pass channel count and mods
  //   But you can tell it attempts to mitigate the issue.
    getAssetTexture( assetName, channels=null, mods=null ){
      this.log("Get Internal Texture - ", assetName);
      let curTexturePath = this.pxlUtils.assetRoot + assetName;
      if(!channels){
        let assetSplit = assetName.split(".");
        let assetExt = assetSplit.pop().toLowerCase();
        if( assetExt=="jpg" || assetExt=="jpeg"  ){
          channels=3;
        }else if( assetExt=="png" ){
          channels=4;
        }
      }
      if(!mods){
        mods={"encoding":LinearSRGBColorSpace, "magFilter":LinearFilter, "minFilter":LinearFilter};
      }

      let textureRead = this.pxlUtils.loadTexture( curTexturePath, channels, mods );
      return textureRead;
    }
    
    
    // A screen filled plane to render outside of effect composer passes
    buildBackgroundObject( customUniforms={}, bgVert=null, bgFrag=null){
        let geo = new PlaneBufferGeometry();
        
        let bgUniforms={}
        Object.assign( bgUniforms, customUniforms );
        
        if( bgVert==null || typeof(bgVert)!="string"){
            bgVert=this.pxlShaders.scene.bgScreenVert();
        }
        if( bgFrag==null || typeof(bgFrag)!="string"){
            bgFrag=this.pxlShaders.scene.bgScreenFrag();
        }
        
        let mtl = this.pxlFile.pxlShaderBuilder( bgUniforms, bgVert, bgFrag );
        mtl.side=DoubleSide;
        mtl.depthTest=true;
        mtl.depthWrite=false;
        //mtl.transparent=true;
        let bgMesh = new Mesh( geo, mtl );
        bgMesh.frustumCulled = false;
        
        return bgMesh;
    }
    
  // In-Scene clickables
  clickUserDetect(){
    
    // Current Room Obj
    let curRoomObj = this.roomSceneList[ this.currentRoom ];

    // Cast mouse or touch position to NDC
    let mouseScreenSpace = this.pxlUtils.screenToNDC( this.pxlDevice.mouseX, this.pxlDevice.mouseY, this.pxlDevice.sW, this.pxlDevice.sH );
    
    // Get clickable objects under mouse
    let rayHits={};
    if( curRoomObj?.hasColliderType && curRoomObj.hasColliderType( COLLIDER_TYPE.CLICKABLE ) ){
      let curObjList = curRoomObj.getColliders( COLLIDER_TYPE.CLICKABLE );
      rayHits = this.pxlColliders.castInteractRay( this.currentRoom, curObjList, this.pxlCamera.camera, mouseScreenSpace );
    }

    // Get nearest object hit,
    //   rayHits.order is an array of objects hit, in order of distance
    if( rayHits.hasOwnProperty("order") && rayHits.order.length > 0 ){
      let objHit = rayHits.order[0];
      this.clickableActions(objHit.name);
      return;
    }

    // -- -- --

    // If no clickable object hit, check for promo clickables
    rayHits = {};
    rayHits = this.pxlColliders.castInteractRay( this.currentRoom, this.promoClickable, this.pxlCamera.camera, mouseScreenSpace );

    if(rayHits.hasOwnProperty("order") && rayHits.order.length > 0){
      let promoHit = rayHits.order[0];
      this.promoActions( promoHit );
    }
  }

  clickableActions(action=null){
    if(action == "CallToAction" && this.clickablePrevActiveObject){
      this.pxlGuiDraws.ctaBuildPopup();
      this.objectClickableObjectList[this.clickablePrevActiveObject]['Inactive'].visible=true;
      this.objectClickableObjectList[this.clickablePrevActiveObject]['Hover'].visible=false;
      this.clickablePrevActiveObject=null;
    }
  }
    
  promoActions(pName=null){
        let pLink=pName.userData.video;
        let pScreen=pName.name;
        
        if( this.promoClickableLinks.hasOwnProperty( pLink ) ){
            var link= window.open( this.promoClickableLinks[pLink], "_blank");
            link.focus();
        }
  }
  // Hover over clickable
  hoverUserDetect(){
    
    // Current Room Obj
    let curRoomObj = this.roomSceneList[ this.currentRoom ];

    // Cast mouse or touch position to NDC
    let mouseScreenSpace = this.pxlUtils.screenToNDC( this.pxlDevice.mouseX, this.pxlDevice.mouseY, this.pxlDevice.sW, this.pxlDevice.sH );
    
    // Get hoverable objects under mouse
    let rayHits={};
    if( curRoomObj?.hasColliderType && (curRoomObj.hasColliderType( COLLIDER_TYPE.CLICKABLE ) || curRoomObj.hasColliderType( COLLIDER_TYPE.HOVERABLE )) ){
      // Combine objectClickable with objectHoverable This may change
      let curObjList = [ ...curRoomObj.getColliders( COLLIDER_TYPE.CLICKABLE ), ...curRoomObj.getColliders( COLLIDER_TYPE.HOVERABLE ) ];
      rayHits = this.pxlColliders.castInteractRay( this.currentRoom, curObjList, this.pxlCamera.camera, mouseScreenSpace );
    }

    // Get nearest object hit,
    //   rayHits.order is an array of objects hit, in order of distance
    if( rayHits.hasOwnProperty("order") && rayHits.order.length > 0 ){
      let objHit = rayHits.order[0];
      this.pxlDevice.setCursor("help");
      if(this.objectClickableObjectList[objHit.name]){
        if(this.clickablePrevActiveObject==null){
          this.clickablePrevActiveObject=objHit.name;
        }
        this.objectClickableObjectList[objHit.name]['Inactive'].visible=false;
        this.objectClickableObjectList[objHit.name]['Hover'].visible=true;
      }
      return;
    }else{
      if(this.clickablePrevActiveObject){
        this.objectClickableObjectList[this.clickablePrevActiveObject]['Inactive'].visible=true;
        this.objectClickableObjectList[this.clickablePrevActiveObject]['Hover'].visible=false;
        this.clickablePrevActiveObject=null;
      }
      this.pxlDevice.setCursor("grab");
    }

    // -- -- --

    // If no clickable object hit, check for promo clickables
    rayHits = {};
    rayHits = this.pxlColliders.castInteractRay( this.currentRoom, this.promoClickable, this.pxlCamera.camera, mouseScreenSpace );

    if(rayHits.hasOwnProperty("order") && rayHits.order.length > 0){
      let promoHit = rayHits.order[0];
      this.pxlDevice.setCursor("alias");
      if(this.promoClickableObjectList[promoHit.name]){
        if(this.promoPrevActiveObject==null){
          this.promoPrevActiveObject=promoHit.name;
        }
        this.promoClickableObjectList[promoHit.name].x=1;
      }
    }else{
      if(this.promoPrevActiveObject){
        this.promoClickableObjectList[this.promoPrevActiveObject].x=.1;
        this.promoPrevActiveObject=null;
      }
      this.pxlDevice.setCursor("grab");
    }
  }

  // Set composer uniforms
  updateCompUniforms(exposure=null){
    if(exposure){
      this.pxlRenderSettings.exposure=exposure*this.pxlRenderSettings.mult;
    }
    if(this.mapOverlayPass){
      this.mapMotionBlurPass.uniforms.exposure.value = this.pxlRenderSettings.exposure;
      this.mapOverlayHeavyPass.uniforms.exposure.value = this.pxlRenderSettings.exposure;
      this.mapOverlayPass.uniforms.exposure.value = this.pxlRenderSettings.exposure;
      this.mapOverlaySlimPass.uniforms.exposure.value = this.pxlRenderSettings.exposure;
      //this.blurScreenMerge.uniforms.exposure.value = this.pxlRenderSettings.exposure;
    }
  }

// -- -- -- -- -- -- -- -- -- -- -- //

  // Event Helpers
  sendRoomMessage( roomName, messageType, messageValue ){
    if( this.roomSceneList[ roomName ] ){
      this.roomSceneList[ roomName ].onMessage( messageType, messageValue );
    }
  }


// -- -- -- -- -- -- -- -- -- -- -- //

  // Build composers and passes
  buildComposers(){
        
        // Set up swapable frame buffers, for prior frame reads
        /*EffectComposer.prototype.swapBuffer = ()=>{
            let tmpBuffer = this.renderTarget2;
            this.renderTarget2 = this.renderTarget1;
            this.renderTarget1 = tmpBuffer;
        };*/
        
    ///////////////////////////////////////////////////
    // -- SCENE WIDE MATERIALS  -- -- -- -- -- -- -- //
    ///////////////////////////////////////////////////

    this.mapWorldPosMaterial=new ShaderMaterial({
      uniforms:{
        camNear: { type:"f", value: this.pxlCamera.camera.near },
        camFar: { type:"f", value: this.pxlCamera.camera.far }
      },
      vertexShader: this.pxlShaders.rendering.worldPositionVert(),
      fragmentShader: this.pxlShaders.rendering.worldPositionFrag()
    });
    //this.mapWorldPosMaterial.side=DoubleSide;
    this.mapWorldPosMaterial.side=FrontSide;
    this.mapWorldPosMaterial.name="mapWorldPosMaterial";
      
    ///////////////////////////////////////////////////
    // -- 2-Step Blur Composer  -- -- -- -- -- -- -- //
    ///////////////////////////////////////////////////

    this.blurComposer = new EffectComposer(this.engine);
    
    this.shaderPasses.blurXShaderPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          time:{ value:this.time },
          tDiffuse: { value: null },
          pDiffuse: { value: null },
          resUV: { value: this.pxlDevice.screenRes },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.directionalBlurPass( "pDiffuse", [1,0], 4, 1.8 ),
        defines: {}
      } ), "tDiffuse"
    );
    this.shaderPasses.blurXShaderPass.material.uniforms.pDiffuse = this.scene.renderGlowTarget.texture;
    this.shaderPasses.blurXShaderPass.material.transparent = true;
    this.shaderPasses.blurXShaderPass.needsSwap = true;
    this.shaderPasses.blurXShaderPass.enabled=false;
    this.shaderPasses.blurXShaderPass.name="blurXShaderPass";
    this.blurComposer.addPass( this.shaderPasses.blurXShaderPass );
    
    
    this.shaderPasses.dirBlurCopyPass = new ShaderPass(CopyShader);
    this.shaderPasses.dirBlurCopyPass.enabled=false;
    this.shaderPasses.dirBlurCopyPass.name="dirBlurCopyPass";
    this.blurComposer.addPass(this.shaderPasses.dirBlurCopyPass);
    
    this.shaderPasses.blurYShaderPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          time:{ value:this.time },
          tDiffuse: { value: null },
          //pDiffuse: { value: this.scene.renderGlowTarget.texture },
          //pDiffuse: { value: this.blurComposer.writeBuffer.texture },
          pDiffuse: { value: null },
          resUV: { value: this.pxlDevice.screenRes },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.directionalBlurPass( "pDiffuse", [0,1], 4, 1.3 ),
        defines: {}
      } ), "tDiffuse"
    );
    this.shaderPasses.blurYShaderPass.material.uniforms.pDiffuse = this.scene.renderGlowTarget.texture;
    this.shaderPasses.blurYShaderPass.material.transparent = true;
    this.shaderPasses.blurYShaderPass.enabled=false;
    this.shaderPasses.blurYShaderPass.name="blurYShaderPass";
    this.blurComposer.addPass( this.shaderPasses.blurYShaderPass );
  
    
    this.shaderPasses.scatterMixShaderPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          time:{ value:this.time },
          tDiffuse: { value: null },
          pDiffuse: { value: null },
          resUV: { value: this.pxlDevice.screenRes },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.mixBlurShaderPass(),
        defines: {}
      } ), "tDiffuse"
    );
    this.shaderPasses.scatterMixShaderPass.material.uniforms.pDiffuse = this.scene.renderGlowTarget.texture;
    this.shaderPasses.scatterMixShaderPass.material.transparent = true;
    this.shaderPasses.scatterMixShaderPass.enabled=false;
    this.shaderPasses.scatterMixShaderPass.name="scatterMixShaderPass";
    this.blurComposer.addPass( this.shaderPasses.scatterMixShaderPass );
    
      
    // Set Anti-Aliasing Quality
    if( this.pxlOptions.antiAliasing == ANTI_ALIASING.LOW){
      this.shaderPasses.scatterMixShaderPass.enabled=true;
    }else if( this.pxlOptions.antiAliasing == ANTI_ALIASING.MEDIUM){
      this.shaderPasses.blurXShaderPass.enabled=true;
      this.shaderPasses.dirBlurCopyPass.enabled=true;
      this.shaderPasses.blurYShaderPass.enabled=true;
    }else if( this.pxlOptions.antiAliasing == ANTI_ALIASING.HIGH ){
      this.shaderPasses.blurXShaderPass.enabled=true;
      this.shaderPasses.dirBlurCopyPass.enabled=true;
      this.shaderPasses.blurYShaderPass.enabled=true;
      this.shaderPasses.scatterMixShaderPass.enabled=true;
    }




    ///////////////////////////////////////////////////
    // -- POST PROCESSING; MAIN MENU  -- -- -- -- -- //
    ///////////////////////////////////////////////////
    // Post Processing
    
    this.mapComposerMotionBlur=new EffectComposer(this.engine);
    
    this.mapMotionBlurPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          tDiffuse: { value: null },
          rDiffuse: { value: null },
          exposure:{type:"f",value:this.pxlRenderSettings.exposure},
          time:{ value:this.pxlTimer.msRunner },
          camRotXYZ:{ value:this.pxlCamera.camRotXYZ },
          blurDirCur:{ type:'f',value:this.blurDirCur },
          blurDirPrev:{ type:'f',value:this.blurDirPrev },
          noiseTexture: { value: this.cloud3dTexture },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.motionBlurPostProcess(this.pxlDevice.screenRes,this.pxlDevice.mobile),
        defines: {}
      } ), "tDiffuse"
    );
    this.mapMotionBlurPass.material.uniforms.rDiffuse = this.scene.renderTarget.texture;
    this.mapMotionBlurPass.needsSwap = true;
    this.mapMotionBlurPass.name = "mapMotionBlurPass";
    this.mapComposerMotionBlur.addPass(this.mapMotionBlurPass);
    this.mapMotionBlurPass.enabled=false;
    this.mapComposerMotionBlur.renderToScreen=false;
    //this.mapComposerMotionBlur.autoClear=false;
    
    // -- -- -- -- -- -- -- -- -- -- //
    
    this.mapComposerGlow=new EffectComposer(this.engine);
    
    let renderGlowPass = new RenderPass(this.scene, this.pxlCamera.camera);
    this.mapComposerGlow.addPass(renderGlowPass);
    
    this.blurScreenMerge = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          tDiffuse: { value: null },
          rDiffuse: { value: null },
          mtDiffuse: { value: null },
          exposure:{type:"f",value:this.pxlRenderSettings.exposure}
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.compLayersPostProcess(),
        defines: {}
      } ), "tDiffuse"
    );
    this.blurScreenMerge.material.uniforms.rDiffuse = this.scene.renderTarget.texture;
    // TODO : Motion Blur pass, performance is slow but should be enableable through 'options'
    //this.blurScreenMerge.material.uniforms.mtDiffuse = this.mapComposerMotionBlur.renderTarget2.texture;
    this.blurScreenMerge.material.uniforms.mtDiffuse = this.scene.renderTarget.texture;
    this.blurScreenMerge.needsSwap = true;
    this.blurScreenMerge.name = "blurScreenMerge";
    this.mapComposerGlow.addPass(this.blurScreenMerge);
    
    let glowCopyPassBlur = new ShaderPass(CopyShader);
    glowCopyPassBlur.name = "glowCopyPassBlur";
    this.mapComposerGlow.addPass(glowCopyPassBlur);
    
    //this.mapGlowPass = new UnrealBloomPass( new Vector2( this.pxlDevice.mapW*this.pxlQuality.bloomPercMult, this.pxlDevice.mapH*this.pxlQuality.bloomPercMult ), .28, 0.08, 0.13 );
    //this.mapGlowPass.threshold = this.pxlRenderSettings.bloomThresh;
    //this.mapGlowPass.strength = this.pxlRenderSettings.bloomStrength;
    //this.mapGlowPass.radius = this.pxlRenderSettings.bloomRadius;
    /*this.mapGlowPass = new BloomPass( this.pxlRenderSettings.bloomStrength, 50, 4, 512);
    this.mapGlowPass.threshold = this.pxlRenderSettings.bloomThresh;
    this.mapGlowPass.strength = this.pxlRenderSettings.bloomStrength;
    this.mapGlowPass.radius = this.pxlRenderSettings.bloomRadius;*/
    //this.mapGlowPass.clear=true;
    
    //this.mapComposerGlow.addPass(this.mapGlowPass);
    this.mapComposerGlow.renderToScreen=false;
    this.mapComposerGlow.autoClear=true;

    this.mapOverlayHeavyPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          gamma:{type:"f",value:this.pxlDevice.gammaCorrection},
          exposure:{type:"f",value:this.pxlRenderSettings.exposure},
          time:{ value:this.pxlTimer.msRunner },
          camPos: { value: this.pxlCamera.camera.position },
          ratio:{ type:'f',value: 1 },
          tDiffuse: { value: null },
          rDiffuse: { value: null },
          bloomTexture: { value: null },
          sceneDepth: { value: null },
          scenePos: { value: null },
          noiseTexture: { value: this.cloud3dTexture },
          fogMult: { value: this.fogMult },
          proximityMult: { value: 1 },
          //bloomTexture: { value: this.mapComposerMotionBlur.renderTarget2.texture }
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.finalOverlayHeavyShader(),
        defines: {}
      } ), "tDiffuse"
    );
    this.mapOverlayHeavyPass.material.uniforms.rDiffuse = this.scene.renderTarget.texture;
    this.mapOverlayHeavyPass.material.uniforms.bloomTexture = this.mapComposerGlow.renderTarget2.texture;
    this.mapOverlayHeavyPass.material.uniforms.sceneDepth = this.scene.renderTarget.depthTexture;
    this.mapOverlayHeavyPass.material.uniforms.scenePos = this.scene.renderWorldPos.texture;
    this.mapOverlayHeavyPass.needsSwap = true;
    this.mapOverlayHeavyPass.name = "mapOverlayHeavyPass";

    this.mapOverlayPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          gamma:{type:"f",value:this.pxlDevice.gammaCorrection},
          exposure:{type:"f",value:this.pxlRenderSettings.exposure},
          time:{ value:this.pxlTimer.msRunner },
                    camPos: { value: this.pxlCamera.camera.position },
          ratio:{ type:'f',value: 1 },
          tDiffuse: { value: null },
          rDiffuse: { value: null },
          bloomTexture: { value: null },
          sceneDepth: { value: null },
          scenePos: { value: null },
          noiseTexture: { value: this.cloud3dTexture },
          fogMult: { value: this.fogMult },
          proximityMult: { value: 1 },
          //bloomTexture: { value: this.mapComposerMotionBlur.renderTarget2.texture }
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.finalOverlayShader(),
        defines: {}
      } ), "tDiffuse"
    );
    this.mapOverlayPass.material.uniforms.rDiffuse = this.scene.renderTarget.texture;
    this.mapOverlayPass.material.uniforms.bloomTexture = this.mapComposerGlow.renderTarget2.texture;
    this.mapOverlayPass.material.uniforms.sceneDepth = this.scene.renderTarget.depthTexture;
    this.mapOverlayPass.material.uniforms.scenePos = this.scene.renderWorldPos.texture;
    this.mapOverlayPass.needsSwap = true;
    this.mapOverlayPass.name = "mapOverlayPass";
    
    this.mapOverlaySlimPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          gamma:{type:"f",value:this.pxlDevice.gammaCorrection},
          exposure:{type:"f",value:this.pxlRenderSettings.exposure},
          time:{ value:this.pxlTimer.msRunner },
          camPos: { value: this.pxlCamera.camera.position },
          ratio:{ type:'f',value: 1 },
          tDiffuse: { value: null },
          rDiffuse: { value: null },
          bloomTexture: { value: null },
          sceneDepth: { value: null },
          fogMult: { value: this.fogMult },
          proximityMult: { value: 1 },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.finalOverlaySlimShader(),
        defines: {}
      } ), "tDiffuse"
    );
    this.mapOverlaySlimPass.material.uniforms.rDiffuse = this.scene.renderTarget.texture;
    //bloomTexture: { value: this.mapComposerMotionBlur.renderTarget2.texture }
    this.mapOverlaySlimPass.material.uniforms.bloomTexture = this.mapComposerGlow.renderTarget2.texture;
    this.mapOverlaySlimPass.material.uniforms.sceneDepth = this.scene.renderTarget.depthTexture;
    this.mapOverlaySlimPass.needsSwap = true;
    this.mapOverlaySlimPass.name = "mapOverlaySlimPass";

    // -- -- -- -- -- -- -- -- -- -- //
    
    this.mapGlowPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          time:{ value:this.pxlTimer.msRunner },
          ratio:{ type:'f',value: 1 },
          tDiffuse: { value: null },
          rDiffuse: { value: null },
          sceneDepth: { value: null },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.glowPassPostProcess(),
        defines: {}
      } ), "tDiffuse"
    );
    this.mapGlowPass.material.uniforms.rDiffuse = this.scene.renderGlowTarget.texture;
    this.mapGlowPass.material.uniforms.sceneDepth = this.scene.renderTarget.depthTexture;
    this.mapGlowPass.needsSwap = true;
    this.mapGlowPass.name = "mapGlowPass";

    // -- -- -- -- -- -- -- -- -- -- //
    
    this.mapComposer = new EffectComposer(this.engine);
    
    this.mapComposer.addPass( this.mapOverlayHeavyPass );
    this.mapComposer.addPass( this.mapOverlayPass );
    this.mapComposer.addPass( this.mapOverlaySlimPass );
    this.mapComposer.addPass( this.mapGlowPass );
    this.mapOverlayHeavyPass.enabled=false;
    this.mapOverlayPass.enabled=false;
    //this.mapOverlayPass.autoClear=true;
    //this.mapOverlaySlimPass.enabled=false;
    
        // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
    this.pxlUser.lKingWarp=new Vector2( ...this.pxlUser.lKingInactive );
        
    this.lizardKingPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          tDiffuse: { value: null },
          time:{ value:this.pxlTimer.msRunner },
          ratio: { value: this.pxlDevice.screenRes },
          noiseTexture: { value: this.cloud3dTexture },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.lKingPostProcess(),
        defines: {}
      } ), "tDiffuse"
    );
        this.pxlUser.lizardKingPass=this.lizardKingPass;
        this.lizardKingPass.enabled=false;
        this.pxlUser.lizardKingPass.name = "lizardKingPass";
    
        // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
    this.pxlUser.starFieldPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          tDiffuse: { value: null },
          time:{ value:this.pxlTimer.msRunner },
          ratio: { value: this.pxlDevice.screenRes },
          noiseTexture: { value: this.cloud3dTexture },
          starTexture: { value: this.pxlUtils.loadTexture(this.pxlUtils.assetRoot+"uv_starNoise.jpg", null, {"encoding":LinearSRGBColorSpace}) },
        },
        vertexShader: this.pxlShaders.rendering.sFieldPostProcessVert(),
        fragmentShader: this.pxlShaders.rendering.sFieldPostProcessFrag(),
        defines: {}
      } ), "tDiffuse"
    );
        this.pxlUser.starFieldPass.enabled=false;
        this.pxlUser.starFieldPass.name = "starFieldPass";
    
        // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
    this.pxlUser.crystallinePass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          tDiffuse: { value: null },
          tDiffusePrev: {type:'t', value: null },
          time:{ value:this.pxlTimer.msRunner },
          ratio: { value: this.pxlDevice.screenRes },
          noiseTexture: { value: this.cloud3dTexture },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.iZoomPostProcess(),
        defines: {}
      } ), "tDiffuse"
    );
        this.pxlUser.crystallinePass.enabled=false;
        this.pxlUser.crystallinePass.name = "crystallinePass";
    
        
        
        // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //

    
    if( this.pxlOptions.postProcessPasses.chromaticAberrationPass ){
      this.chromaticAberrationPass = new ShaderPass(
        new ShaderMaterial( {
          uniforms: {
            tDiffuse: { value: null },
            ratio: { value: this.pxlDevice.screenRes },
            warpMult: { value: this.chroAberMult },
            chroAberUVTexture: { value: this.chroAberUVTexture },
            chroAberUVAlpha: { value: this.chroAberUVAlpha },
            lKing: { value: this.pxlUser.lKingWarp },
          },
          vertexShader: this.pxlShaders.core.defaultVert(),
          fragmentShader: this.pxlShaders.rendering.chroAberPostProcess(),
          defines: {}
        } ), "tDiffuse"
      );
      this.chromaticAberrationPass.enabled=false;
      this.chromaticAberrationPass.name = "chromaticAberrationPass";
      this.mapComposer.addPass( this.chromaticAberrationPass );
    }
    
    if( this.pxlOptions.postProcessPasses.lizardKingPass ){
      this.mapComposer.addPass( this.lizardKingPass );
    }
    //if( !this.mobile ) {
    if( this.pxlOptions.postProcessPasses.starFieldPass ){
      this.mapComposer.addPass( this.pxlUser.starFieldPass );
    }
    if( this.pxlOptions.postProcessPasses.crystallinePass ){
      this.mapComposer.addPass( this.pxlUser.crystallinePass );
    }
    
    if( this.pxlOptions.postProcessPasses.mapComposerWarpPass ){
      this.mapComposerWarpPass = new ShaderPass(
        new ShaderMaterial( {
          uniforms: {
            time:{ value:this.pxlTimer.msRunner },
            fader:{ value:this.warpVisualFader },
            tDiffuse: { value: null },
            noiseTexture: { value: this.cloud3dTexture },
            animTexture: { value: this.blockAnimTexture  },
            //bloomTexture: { value: this.mapComposerMotionBlur.renderTarget2.texture }
          },
          vertexShader: this.pxlShaders.core.camPosVert(),
          fragmentShader: this.pxlShaders.rendering.warpPostProcess(),
          defines: {}
        } ), "tDiffuse"
      );
      this.mapComposerWarpPass.needsSwap = true;
      this.mapComposerWarpPass.enabled=false;
      this.mapComposerWarpPass.name = "mapComposerWarpPass";
      this.mapComposer.addPass( this.mapComposerWarpPass );
    }
        // 8 Samples
    this.mapBoxAAPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          tDiffuse: { value: null },
          resUV:{ type:'f',value:this.pxlDevice.screenRes },
          ratio:{ type:'f',value: 1 },
          gamma:{type:"f",value:this.pxlDevice.gammaCorrection},
        },
        vertexShader: this.pxlShaders.core.camPosVert(),
        fragmentShader: this.pxlShaders.rendering.boxAntiAliasPass(),
        defines: {}
      } ), "tDiffuse"
    );
    this.mapBoxAAPass.enabled=false;
    this.mapBoxAAPass.name = "mapBoxAAPass";
    this.mapComposer.addPass( this.mapBoxAAPass );
        
        // 4 samples
    this.mapCrossAAPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          tDiffuse: { value: null },
          resUV:{ type:'f',value:this.pxlDevice.screenRes },
          ratio:{ type:'f',value: 1 },
          gamma:{type:"f",value:this.pxlDevice.gammaCorrection},
        },
        vertexShader: this.pxlShaders.core.camPosVert(),
        fragmentShader: this.pxlShaders.rendering.crossAntiAliasPass(),
        defines: {}
      } ), "tDiffuse"
    );
    this.mapCrossAAPass.enabled=false;
    this.mapCrossAAPass.name = "mapCrossAAPass";
    this.mapComposer.addPass( this.mapCrossAAPass );
    
    this.mapComposer.autoClear=true;
    
    // -- -- -- -- -- -- -- -- -- -- //
    
    // External Room composer
    let bootScene= this.roomSceneList[this.bootRoom].scene; // this.roomSceneList['ShadowPlanet'].scene ||
    this.roomComposer=new EffectComposer(this.engine);

    this.roomRenderPass = new RenderPass(bootScene, this.pxlCamera.camera);
    this.roomRenderPass.name = "roomRenderPass";
    this.roomComposer.addPass(this.roomRenderPass);
        
        
    this.roomNameList.forEach( (r)=>{
      if( r != this.mainRoom){
        let curPass=this.roomSceneList[ r ].applyRoomPass( this.roomComposer );
        if( curPass ){
            curPass.enabled=false;
            this.roomComposer.addPass( curPass );
        }
      }
    });
        
    
    this.roomBloomPass = new UnrealBloomPass( new Vector2( this.pxlDevice.mapW*.5, this.pxlDevice.mapH*.5 ), 1.5, 0.8, 0.85 );
    this.roomBloomPass.threshold = this.pxlRenderSettings.bloomThresh;
    this.roomBloomPass.strength = this.pxlRenderSettings.bloomStrength;
    this.roomBloomPass.radius = this.pxlRenderSettings.bloomRadius;
    this.roomBloomPass.name = "roomBloomPass";
    this.roomComposer.addPass( this.roomBloomPass );
    
    
    if( this.pxlOptions.postProcessPasses.roomGlowPass ){
      this.roomGlowPass = new ShaderPass(
        new ShaderMaterial( {
          uniforms: {
            time:{ value:this.pxlTimer.msRunner },
            ratio:{ type:'f',value: 1 },
            tDiffuse: { value: null },
            gDiffuse: { value: null },
            rDiffuse: { value: null },
            sceneDepth: { value: null },
          },
          vertexShader: this.pxlShaders.core.defaultVert(),
          fragmentShader: this.pxlShaders.rendering.glowPassPostProcess(),
          defines: {}
        } ), "tDiffuse"
      );
      
      //gDiffuse: { value: this.scene.renderGlowTarget.texture },
      //gDiffuse: { value: this.blurComposer.renderTarget1.texture },
      this.roomGlowPass.material.uniforms.gDiffuse = this.blurComposer.writeBuffer.texture;
      this.roomGlowPass.material.uniforms.rDiffuse = this.blurComposer.renderTarget2.texture;
      this.roomGlowPass.material.uniforms.sceneDepth = this.scene.renderTarget.depthTexture;
      this.roomGlowPass.needsSwap = true;
      this.roomGlowPass.name = "roomGlowPass";

      this.roomComposer.addPass( this.roomGlowPass );
    }
    
    if( this.pxlOptions.postProcessPasses.chromaticAberrationPass ){
      this.roomComposer.addPass( this.chromaticAberrationPass );
    }
    
    if( this.pxlOptions.postProcessPasses.lizardKingPass ){
      this.roomComposer.addPass( this.lizardKingPass );
    }
    
    if( this.pxlOptions.postProcessPasses.starFieldPass ){
      this.roomComposer.addPass( this.pxlUser.starFieldPass );
    }

    if( this.pxlOptions.postProcessPasses.crystallinePass ){
      this.roomComposer.addPass( this.pxlUser.crystallinePass );
    }

    if( this.pxlOptions.postProcessPasses.mapComposerWarpPass ){
      this.roomComposer.addPass( this.mapComposerWarpPass );
    }
        
    this.roomComposer.addPass( this.mapCrossAAPass );
    this.roomComposer.addPass( this.mapBoxAAPass );
        
    this.roomComposer.autoClear=true;
        
        // -- -- -- -- -- -- -- -- //
        
    
        // -- -- -- -- -- -- -- -- //
        // Set above, for pass to use renderTarget in uniforms
    this.delayComposer=new EffectComposer(this.engine);
    
    let renderDelayPass = new RenderPass(this.scene, this.pxlCamera.camera);
    //this.delayComposer.addPass(renderDelayPass);
        
    this.delayPass = new ShaderPass(
      new ShaderMaterial( {
        uniforms: {
          tDiffuse: { value: null },
          roomComposer: { type:"f", value : 0 },
          tDiffusePrev: { value: null },
          tDiffusePrevRoom: { value: null },
        },
        vertexShader: this.pxlShaders.core.defaultVert(),
        fragmentShader: this.pxlShaders.rendering.textureStorePass(),
        defines: {}
      } ), "tDiffuse"
    );
    this.delayPass.material.uniforms.tDiffusePrev = this.mapComposer.renderTarget1.texture;
    this.delayPass.material.uniforms.tDiffusePrevRoom = this.roomComposer.renderTarget1.texture;
    //this.delayPass.needsSwap = true;
    this.delayPass.clear=false;
    this.delayComposer.addPass( this.delayPass );
    this.delayComposer.renderToScreen=false;
    this.delayComposer.autoClear=false;
        
    this.pxlUser.crystallinePass.uniforms.tDiffusePrev.value = this.delayComposer.renderTarget2.texture;
  }
  
  setExposure(curExp){
    let animPerc=1;
    //curExp= this.pxlCamera.uniformScalars.curExp + curExp*this.pxlCamera.uniformScalars.brightBase*animPerc; 
    curExp= curExp*animPerc; 
    this.pxlCamera.uniformScalars.exposureUniformBase=curExp;
    // Set scene exposure on post-process composer passes 
    this.updateCompUniforms(curExp);
  }
  
  stepWarpPass(){
    if( this.warpVisualActive ){
      let curPerc= ( this.pxlTimer.curMS - this.warpVisualStartTime ) / this.warpVisualMaxTime[this.pxlCamera.warpType];
      let fUp=Math.min( 1, curPerc*3 );
      let fDown=Math.min( 1, 3-curPerc*3 );
      
      if(fUp==1 && fDown==1 && this.pxlCamera.warpActive){
        this.pxlCamera.warpCamRun();
      }
      
      this.warpVisualFader.x=fUp;
      this.warpVisualFader.y=fDown;
      if( fDown <= 0){
        this.stopWarpVisual();
      }
    }
  }
  
  checkUserVideoStatus(curId){
  }
    
  remoteUserUpdateMedia( curId, video=false, audio=null){
        //
  }
    
    userRemoveRemoteData( curId ){
        //
    }
    
    
  stepShaderValues(){ // ## Switch variables in shaders to three variables to avoid this whole thing  
    this.stepShaderFuncArr.forEach((x)=>{
      if(typeof(x)=="object"){
        x.step();
      }else if(typeof(x)=="string"){
        console.log("shader trigger?");
        console.log(x);
        //(x);
      }
    });
    
  }

  stepAnimatedObjects(){
    if(this.pxlUser.itemListNames.length > 0){
      this.pxlUser.itemListNames.forEach( (i)=>{
                this.pxlUser.itemList[i].rotation.y=this.pxlTimer.msRunner.x*this.pxlUser.itemRotateRate;
      });
    }
  }

  initWarpVisual( visualType ){
    this.warpVisualActive=true;
    this.warpVisualFader.x=0;
    this.warpVisualFader.y=1;
    this.warpVisualStartTime=this.pxlTimer.curMS;
    
    if( this.mapComposerWarpPass ){
      this.mapComposerWarpPass.enabled=true;
    }
  }
  stopWarpVisual(){
    this.warpVisualActive=false;
    this.warpVisualFader.x=1;
    this.warpVisualFader.y=0;
    
    if( this.mapComposerWarpPass ){
      this.mapComposerWarpPass.enabled= !!this.pxlUser.iZoom;
    }
  }
  
// Function required, but no prep needed
  prepPortalRender(){}
// Function required, but no cleanup needed
  cleanupPortalRender(){}
// Set the Room Warp Portal plane to display the render from that environment
  setPortalTexture(texture, toRoom){
    this.roomWarpVisuals[toRoom].material.map=texture;
  }
  warpPortalQueue(){
    return Object.keys(this.roomSceneList).reverse(); // So .pop'ing goes the correct direction
  }
    
  getSceneSnapshot(curScene){
    let prepRoom=this.roomSceneList[curScene];
    
    //this.pxlCamera.setTransform( prepRoom.camInitPos, prepRoom.camInitLookAt );
    this.engine.setRenderTarget(prepRoom.warpZoneRenderTarget);
    //this.engine.clear();
    
    prepRoom.prepPortalRender();
    this.engine.render(  prepRoom.scene || prepRoom.scene, this.pxlCamera.camera );
    prepRoom.cleanupPortalRender();
    /*
    if( curScene == this.mainRoom ){
      //this.mapRender();
      
      //this.warpPortalTextures[ curScene ] = this.mapComposer.renderTarget1.texture.clone();
      
    }else{
      //prepRoom.step();
    
      prepRoom.prepPortalRender();
      this.engine.render(  prepRoom.scene, this.pxlCamera.camera );
      prepRoom.cleanupPortalRender();
      
      this.warpPortalTextures[ curScene ] = this.scene.renderTarget.clone();
    
      prepRoom.warpPortalTexture=this.warpPortalTextures[ curScene ];
      //prepRoom.setPortalTexture( prepRoom.warpPortalTexture );
      //prepRoom.setPortalTexture( this.warpPortalTextures[ this.mainRoom ] );
      prepRoom.setPortalTexture( this.cloud3dTexture );//  this.warpPortalTextures[ this.mainRoom ] );
      this.setPortalTexture( this.warpPortalTextures[ curScene ], curScene );
    }*/
    
    this.engine.setRenderTarget(null);
  
  }

  mapRender(anim=true){
    if(this.pxlTimer.active){
        this.step();
    }
    
    if( this.pxlTimer.curMS > this.nextRenderMS || anim==false ){
      this.prevRenderMS = this.nextRenderMS;
      this.nextRenderMS = this.pxlTimer.curMS + this.renderInterval;

      // Render appropriate room
      this.stepShaderValues();
      this.stepAnimatedObjects();
      
      let curRoom=this.roomSceneList[this.currentRoom];
      if(curRoom && curRoom.booted){
        curRoom.step();
        curRoom.camera.layers.disable( this.pxlEnums.RENDER_LAYER.SKY );
        this.engine.setRenderTarget(curRoom.scene.renderTarget);
        this.engine.clear();
        this.engine.render( curRoom.scene, curRoom.camera);
        this.engine.setRenderTarget(null);
        curRoom.camera.layers.enable( this.pxlEnums.RENDER_LAYER.SKY );
        
        if( false && this.pxlQuality.settings.fog>0 ){
          this.pxlCamera.camera.layers.disable( 1 );
          
          curRoom.scene.overrideMaterial=this.mapWorldPosMaterial;
          this.engine.setRenderTarget(this.scene.renderWorldPos);
          this.engine.clear();
          this.engine.render( curRoom.scene, this.pxlCamera.camera);
          curRoom.scene.overrideMaterial=null;
        
          this.pxlCamera.camera.layers.enable( 1 );
          this.engine.setRenderTarget(null);
        }
        
        if( this.mapComposerGlow && ( this.pxlQuality.settings.bloom || this.pxlQuality.settings.fog ) ){ //  || this.pxlQuality.settings.motion ){ 
          this.mapComposerGlow.render();
        }
        
        this.mapRenderBlurStack( curRoom, curRoom.camera, curRoom.scene, this.scene.renderGlowTarget)
        
        this.roomComposer.render();
        //this.engine.render( this.roomSceneList[this.currentRoom].scene, this.pxlCamera.camera);
      }
      
      if( this.pxlUser.iZoom ){
        this.delayComposer.render();
      }
    }else if( this.pxlOptions.subTickCalculations ){
      // Step room calculations for render-independent user input and collision calculations
      let curRoom=this.roomSceneList[this.currentRoom];
      if(curRoom && curRoom.booted){
        curRoom.step();
      }
    }
        
    if(this.pxlTimer.active && anim){
      requestAnimationFrame( ()=>{ this.mapRender(); });
    }
  }
  
  mapRenderBlurStack( curRoom, camera, scene, target ){
    if(this.blurComposer){
      if(curRoom.geoList["GlowPass"]){
        curRoom.geoList["GlowPass"].forEach((g)=>{
          if( g.userData.hasOwnProperty('GlowPerc') ){
            let multVal = g.userData['GlowPerc']
            if( g.material.hasOwnProperty('uniforms') && g.material.uniforms.mult ){
              g.material.uniforms.mult.value = multVal;
            }
          }
        });
        
        if( curRoom.geoList.hasOwnProperty('GlowPassMask') ){
          curRoom.geoList['GlowPassMask'].forEach( (m)=>{
            if( m.material.uniforms && m.material.uniforms.cdMult ){
              m.material.uniforms.cdMult.value = 0;
            }
          });
        }
      }
      
      //this.pxlEnv.pxlEnums.RENDER_LAYER SCENE PARTICLES GLOW
      camera.layers.disable( this.pxlEnums.RENDER_LAYER.SCENE );
      camera.layers.disable( this.pxlEnums.RENDER_LAYER.PARTICLES );
      camera.layers.disable( this.pxlEnums.RENDER_LAYER.SKY );
      
      this.engine.setRenderTarget(target);
      let bgCd=0x000000;
      let curgb = scene.background.clone()
      scene.background.set( bgCd );
      this.engine.setClearColor(bgCd, 0);
      //this.engine.clear();
      this.engine.render( scene, camera);
      //this.scene.overrideMaterial=null;
      scene.background.r=curgb.r;
      scene.background.g=curgb.g;
      scene.background.b=curgb.b;
      
      camera.layers.enable( this.pxlEnums.RENDER_LAYER.SCENE );
      camera.layers.enable( this.pxlEnums.RENDER_LAYER.PARTICLES );
      camera.layers.enable( this.pxlEnums.RENDER_LAYER.SKY );
      this.engine.setRenderTarget(null);
      
      if(curRoom.geoList["GlowPass"]){
        curRoom.geoList["GlowPass"].forEach((g)=>{
          if( g.userData.hasOwnProperty('GlowPerc') ){
            if( g.material.hasOwnProperty('uniforms') && g.material.uniforms.mult ){
              g.material.uniforms.mult.value = 1;
            }
          }
        });
        if( curRoom.geoList.hasOwnProperty('GlowPassMask') ){
          curRoom.geoList['GlowPassMask'].forEach( (m)=>{
            if( m.material.uniforms && m.material.uniforms.cdMult ){
              m.material.uniforms.cdMult.value = 1;
            }
          });
        }
      }
      
      this.blurComposer.render();
      this.roomBloomPass.enabled = false;
    }
  }
}