pxlNav/core/QualityController.js


/**
 * @namespace pxlQuality
 * @description Quality settings and benchmarking
 */

import { VERBOSE_LEVEL } from "./Enums.js";
import { Vector3 } from "../../libs/three/three.module.min.js";

export class QualityController{
  constructor( verbose, mobile=false, searchParms={}){
    this.pxlTimer=null;
    this.pxlCookie=null;
    this.pxlDevice=null;
    this.pxlEnv=null;
    this.msLog=0;
    this.prevMS=new Date().getTime()*.001;
    this.autoQuality=false;
    this.percent=1;
    this.verbose=verbose;
        
    this.screenResPerc=1;
    this.mBlurPercMult=mobile?.65:1;
    this.bloomPercMult=mobile?.65:1;
    this.bufferPercMult=1;//mobile?.3:.5,
    this.renderTargetScale=10; // ## Not implemented
    this.percentSteps=[.3,.45,.85];
    this.fpsCounter=new Vector3(30, 0, (new Date().getTime())*.001);; // [ Cur FPS, Frame Count Since Last FPS Check, Last FPS Sample Time ]
    this.countAtLevel=0,
    this.shiftRate=.01;
    this.moduleQualityList=[];
    
    this.qualityStep=-1;
    this.qualityMaxSteps=3;
    this.qualityStepValues=[ .25, .5, .75, 1 ];
    
    this.benchmarkStart=-1;
    this.benchmarkTime=-1;
    this.benchmarkRating=-1;
    this.benchmarkTimes=[9,17];
    
    this.setFromBenchmark=true;
    this.benchmarkQuality=1;
    this.benchmarkQualityRange=[.25, 1];
    
    this.settingsQualityChoice=null;
    this.detailLimitOverride= Object.keys(searchParms).includes("dlimit") ? searchParms["dlimit"] : 0;
    this.detailLimit= this.detailLimitOverride!=null ? this.detailLimitOverride : 0;
    
    // Default Quality Settings & Benchmark Settings
    //   Numbers or Booleans ONLY; or update GuiDraw.setRadioValues()
    // TODO : Movement settings shouldn't be on the Quality controller
    //          "Quality" should strictly remain Render Settings
    this.settings={
      leftRight:true, // Turn, Strafe
      mouse:true, // Drag, Point
      graphics:2, // Low, Medium, High, Custom
      resolution:1, // % Screen Size; [sW,sH] 
      fog:2, // Off, Medium, High
      bloom:true, // Unreal Bloom
      motion:false, // pxlNav Motion Blur // ## Implement
    };
    
    // Low Quality Settings
    this.settingsLow={
      resolution:.5,
      fog:0,
      bloom:false,
      motion:false,
    };
    // Medium Quality Settings
    this.settingsMedium={
      resolution:.75,
      fog:1,
      bloom:true,
      motion:false,
    };
    // High Quality Settings
    this.settingsHigh={
      resolution:1,
      fog:2,
      bloom:true,
      motion:false,
    };
    // Custom Quality Settings
    this.settingsCustom=null;
  }

  setDependencies( pxlNav ){
    this.pxlTimer=pxlNav.pxlTimer;
    this.pxlCookie=pxlNav.pxlCookie;
    this.pxlDevice=pxlNav.pxlDevice;
    this.pxlEnv=pxlNav.pxlEnv;
  }

  init(){
        
    if( this.detailLimitOverride!=null ){
        this.detailLimit = this.detailLimitOverride;
    }else{
        if(this.pxlCookie.hasCookie("detailLimit")){
            this.detailLimit=this.pxlCookie.parseCookie("detailLimit");
        }
    }
    
    if( this.verbose >= VERBOSE_LEVEL.INFO ){
      console.log( "Graphics Limiting is set to level ", this.detailLimit );
    }
    
    //parseDict sets the dict key values, returning true/false if keys missed
    this.setFromBenchmark = !this.pxlCookie.parseDict( this.settings );

    if(this.pxlCookie.hasCookie("leftRight")){
      this.settings.leftRight=this.pxlCookie.parseCookie("leftRight");
    }
    if(this.pxlCookie.hasCookie("mouse")){
      this.settings.mouse=this.pxlCookie.parseCookie("mouse");
    }
        let cookieEntries=Object.keys( this.settingsHigh );
    if(this.pxlCookie.hasCookie("qualitySetting")){
      let qualitySetting=this.pxlCookie.parseCookie("qualitySetting");
            this.settings.graphics=qualitySetting;
            this.settingsQualityChoice=qualitySetting;
    }
  }
  step(){
    this.mapFpsQualitCheck();
  }
  
    // ## Indended for a more universe "Quality Control" system
    //      But not implemented yet.
  attachModule( module=null ){
    if(module){
      this.moduleQualityList.push( module );
    }
  }
  
  startBenchmark(){
    this.pxlTimer.step();
    this.benchmarkStart=this.pxlTimer.curMS;
  }
  endBenchmark(){
    this.pxlTimer.step();
    let bDelta=this.pxlTimer.curMS-this.benchmarkStart;
    this.benchmarkTime=bDelta;
    
    this.benchmarkRating= 1-Math.min(1, Math.max(0, (bDelta-this.benchmarkTimes[0])/(this.benchmarkTimes[1]-this.benchmarkTimes[0]) ));
    
    // Set Benchmark Target Quality Percent to range; currently 25% to 100%
    this.benchmarkQuality= this.benchmarkRating*(this.benchmarkQualityRange[1]-this.benchmarkQualityRange[0]) + this.benchmarkQualityRange[0];
    
    //%=
    //console.log("Benchmark Timing - ",bDelta); 
    //console.log("Benchmark Rating - ",this.benchmarkRating);
    //console.log("Benchmark Quality - ", parseInt(this.benchmarkQuality*100), "%");
    //%
    
    let percStep=Math.min(1, Math.max(0, this.benchmarkRating ));
    percStep=Math.ceil( Math.max(.05,percStep)*this.qualityMaxSteps)-1;
    this.qualityStep=percStep;
    if( this.setFromBenchmark ){
      //%=
      let levelText=["Low","Medium","High"][this.qualityStep];
      //console.log( "Setting Benchmark to "+levelText+" quality.");
      //%
      this.setQualityLevel( this.qualityStep );
    }else{
            if(this.settingsQualityChoice==null){
                this.settingsQualityChoice=3;
            }
            
      this.settingsCustom={};
      let keys=Object.keys( this.settingsHigh ); // Just using this.settingsHigh reference for Keys
      keys.forEach( (k)=>{
        this.settingsCustom[k]=this.settings[k];
      });
    }
        
        this.setDependentSettings();
  }
  
    // ## This might be causing some issues with movement jittering
  mapFpsQualitCheck(){
    if( this.pxlTimer.curMS-this.fpsCounter.z > 1){
      this.fpsCounter.x= this.fpsCounter.y; //':new THREE.Vector3(30, 0, (new Date().getTime())), // [ Cur FPS, Time Since Last FPS Check, Last Sample Time ]
      this.fpsCounter.y=0;
      this.fpsCounter.z=this.pxlTimer.curMS;
      
      let dir=1;
      if(this.fpsCounter.x<15){
        dir=-1;
      }
      let toPerc=Math.min(1, Math.max(0, this.percent+this.shiftRate*dir));
      //datRenderResolutionSlider.setValue(toPerc);
      if(this.autoQuality){
        this.mapAutoQualityUpdate(toPerc, false);
      }
    }
    this.fpsCounter.y+=1;
    this.pxlTimer.prevMS=this.pxlTimer.curMS;
  }

  mapAutoQualityUpdate(percUpdate=null, force=false){
    // Core percentage updates via settings dict
    let percStep=Math.min(1, Math.max(0, percUpdate ));
    percStep=Math.ceil( Math.max(.05,percStep)*this.qualityMaxSteps)-1;
    this.qualityStep=percStep;
  }
  
  setGraphicsSettings( qualitySettings=null, graphicsPreset=3 ){
    if(qualitySettings==null){
      if(this.qualityStep==0){
        qualitySettings=this.settingsLow;
      }else if(this.qualityStep==1){
        qualitySettings=this.settingsMedium;
      }else if(this.qualityStep==2){
        qualitySettings=this.settingsHigh;
      }else{
        return;
      }
    }
    
    if(graphicsPreset==3){
      this.checkCustomDict();
    }
    
    let keys=Object.keys(qualitySettings);
    keys.forEach( (k)=>{
      this.setComponentQuality(k,qualitySettings[k]);
      this.settings[k]=qualitySettings[k];
      if(graphicsPreset==3){
        this.settingsCustom[k]=qualitySettings[k];
      }
    });
    
       // this.pxlEnv.mapRender(false);
    
    // ## I dunno, I need to set more settings per component
    //   Just set em all again!
    /*keys=Object.keys(this.settings);
    keys.forEach( (k)=>{
      if( !["leftRight","mouse","graphics"].includes(k) ){
        this.setComponentQuality(k,this.settings[k], false);
      }
    });*/
       // this.pxlEnv.mapRender(false);
    
    this.setDependentSettings();
    this.colliderPrevObjHit=null;
    
    if( graphicsPreset!=null ){
      this.settings.graphics=graphicsPreset;
    }
  }
  
    reapplySettings(){
    let keys=Object.keys(this.settingsLow);
    keys.forEach( (k)=>{
      //this.setComponentQuality(k,this.settingsLow[k], false);
      //this.setComponentQuality(k,this.settingsHigh[k], false);
      this.setComponentQuality(k,this.settings[k], false);
    });
    this.setDependentSettings();
    }
    
  setQualityLevel(val){
        if( this.pxlDevice.mobile ){
            val=1;
        }
        
    this.setQualityCookie(val);
        
    if(val==0){
      this.setLowQuality();
    }else if(val==1){
      this.setMediumQuality();
    }else if(val==2){
      this.setHighQuality();
    }else if(val==3){
      this.setCustomQuality();
    }
  }    
  
  setLowQuality(){
    this.settingsQualityChoice=0;
    this.setGraphicsSettings(this.settingsLow, 0);
  }
  setMediumQuality(){
    this.settingsQualityChoice=1;
    this.setGraphicsSettings(this.settingsMedium, 1);
  }
  setHighQuality(){
    this.settingsQualityChoice=2;
    this.setGraphicsSettings(this.settingsHigh, 2);
  }
  setCustomQuality(){
    this.checkCustomDict();
    this.settingsQualityChoice=3;
    this.setGraphicsSettings( this.settingsCustom);
  }
  checkCustomDict(){
    if(this.settingsCustom==null){
      this.settingsCustom={};
      let sourceDict=this.settingsHigh;
      if(this.settingsQualityChoice){
        if(this.settingsQualityChoice==0){
          sourceDict= this.settingsLow ;
        }else if(this.settingsQualityChoice==1){
          sourceDict= this.settingsMedium ;
        }else if(this.settingsQualityChoice==2){
          sourceDict= this.settingsHigh ;
        }
      }
      Object.assign( this.settingsCustom, sourceDict );
    }
  }
  
    setQualityCookie(val){
    this.pxlCookie.setCookie( "qualitySetting", val );
    }
    
  // Set values per specific setting change
  //   Render & Navigational Settings
  setComponentQuality( component, val, setCookie=true ){
    switch(component){
      case "leftRight":
        this.settings[component]=val;
        break;
        
      case "mouse":
        this.settings[component]=val;
        break;
        
      case "resolution":
        this.screenResPerc=val;
        this.pxlDevice.resizeRenderResolution();
        this.settings[component]=val;
        if( this.pxlEnv.geoList['snow'] && this.pxlEnv.geoList['snow'].material ){
            this.pxlEnv.geoList['snow'].material.uniforms.pointScale.value = this.pxlEnv.geoList['snow'].pBaseScale * val;
        }
        break;
        
      case "fog":
        if( this.pxlEnv.mapOverlayHeavyPass ) this.pxlEnv.mapOverlayHeavyPass.enabled = val==2;
        if( this.pxlEnv.mapOverlayPass ) this.pxlEnv.mapOverlayPass.enabled = val==1;
        if( this.pxlEnv.mapOverlaySlimPass ) this.pxlEnv.mapOverlaySlimPass.enabled = val==0;
        if( this.pxlEnv.mapBoxAAPass.enabled ) this.pxlEnv.mapBoxAAPass.enabled=val==2;
        if( this.pxlEnv.mapCrossAAPass.enabled ) this.pxlEnv.mapCrossAAPass.enabled=val==1;
                
        if( this.pxlEnv.portaluserScreenIntensity ) this.pxlEnv.portaluserScreenIntensity.x=1;
        //this.pxlEnv.pxlRenderSettings.mult=1;
        if( this.pxlEnv.userScreenIntensity ){
          this.pxlEnv.userScreenIntensity.x = .65 ;
          this.pxlEnv.userScreenIntensity.y =  0 ;
        }
        if(this.pxlEnv.geoList['HDRView']){
            this.pxlEnv.geoList['HDRView'].material.uniforms.cdMult.value = val==0 ? .7 : .3;
        }
        this.settings[component]=val;
        break;
        
      case "bloom":
        this.pxlEnv.portaluserScreenIntensity.x = val ? .4 : 1;
        if( this.pxlEnv.mapGlowPass ) this.pxlEnv.mapGlowPass.enabled = val;
        if( this.pxlEnv.roomBloomPass )  this.pxlEnv.roomBloomPass.enabled = val;
        if( this.pxlEnv.roomGlowPass ) this.pxlEnv.roomGlowPass.enabled = val;
        
        if( this.pxlEnv.userScreenIntensity ){
          this.pxlEnv.userScreenIntensity.x = val ? .65 : .8;
          this.pxlEnv.userScreenIntensity.y = val ? 0 : .8;
        }
        if(this.pxlEnv.geoList['HDRView']){
            this.pxlEnv.geoList['HDRView'].material.uniforms.cdMult.value = val==0 ? .7 : .3;
        }
        
        if( this.pxlEnv.mapMotionBlurPass ) this.pxlEnv.mapMotionBlurPass.enabled=val;  
      
        this.settings[component]=val;
        break;
        
      case "motion":
        if( this.pxlEnv.mapMotionBlurPass ) this.pxlEnv.mapMotionBlurPass.enabled=val;  
      
        this.settings[component]=val;
        break;
        
      default:
        //%=
        //console.error("No Setting Component Updated - ",component,": ",val);
        //%
        break;
    }
    if(setCookie){
      this.pxlCookie.setCookie( component, val );
    }
  }
  setDependentSettings(){
    let multVal=1;
    let portalIntensity=1;
        let circleGateBloom=0;
        let circleGateColor=.6;
    
        let mapMotionBlurPassEnabled = false;
        let mapOverlayHeavyPassEnabled = false;
        let mapOverlayPassEnabled = false;
        let mapOverlaySlimPassEnabled = false;
                  
        let mapBoxAAPassEnabled = false;
        let mapCrossAAPassEnabled = false;

        if( this.settings.fog==2 ){
          mapMotionBlurPassEnabled =false;  
          mapOverlayHeavyPassEnabled = true;
          mapOverlayPassEnabled = false;
          mapOverlaySlimPassEnabled = false;

          mapBoxAAPassEnabled = true;
          mapCrossAAPassEnabled = false;
          multVal=1.00;
          portalIntensity=.5;
                    
          /*if( this.pxlEnv.geoList['skySemiSphere'] ){
              this.pxlEnv.geoList["skySemiSphere"].material.uniforms.fogIntensity.value=1; // Post Process Fog
          }*/
                    
        }else if( this.settings.fog==1 ){
          mapMotionBlurPassEnabled = false;  
          mapOverlayHeavyPassEnabled = false;
          mapOverlayPassEnabled = true;
          mapOverlaySlimPassEnabled = false;
                    
          mapBoxAAPassEnabled = false;
          mapCrossAAPassEnabled = true;
          multVal=1.15;
          portalIntensity=.5;
        }else{
          mapMotionBlurPassEnabled = false;
          mapOverlayPassEnabled = false;
          mapOverlaySlimPassEnabled = true;
                    
          mapBoxAAPassEnabled = false;
          mapCrossAAPassEnabled = false;
          multVal=1.;
          portalIntensity=.4;
        }


        if( this.pxlEnv.mapMotionBlurPass ) this.pxlEnv.mapMotionBlurPass.enabled = mapMotionBlurPassEnabled;
        if( mapOverlayHeavyPassEnabled ) this.pxlEnv.mapOverlayHeavyPass.enabled = mapOverlayHeavyPassEnabled;
        if( mapOverlayPassEnabled ) this.pxlEnv.mapOverlayPass.enabled = mapOverlayPassEnabled;
        if( mapOverlaySlimPassEnabled ) this.pxlEnv.mapOverlaySlimPass.enabled = mapOverlaySlimPassEnabled;
                  
        if( mapBoxAAPassEnabled ) this.pxlEnv.mapBoxAAPass.enabled = mapBoxAAPassEnabled;
        if( mapCrossAAPassEnabled ) this.pxlEnv.mapCrossAAPass.enabled = mapCrossAAPassEnabled;


        
        if( this.settings.bloom ){
          if( this.pxlEnv.mapGlowPass ) this.pxlEnv.mapGlowPass.enabled=true;  

          if( this.pxlEnv.roomBloomPass) this.pxlEnv.roomBloomPass.enabled=true;  

          if( this.pxlEnv.roomGlowPass ) this.pxlEnv.roomGlowPass.enabled=true;  

          this.pxlEnv.userScreenIntensity.x=.65;
          this.pxlEnv.userScreenIntensity.y=0;
                    circleGateBloom=1;
                    circleGateColor=.25;
        }else{
          if( this.pxlEnv.mapGlowPass ) this.pxlEnv.mapGlowPass.enabled=false;
          if( this.pxlEnv.roomBloomPass ) this.pxlEnv.roomBloomPass.enabled=false;
          if( this.pxlEnv.roomGlowPass ) this.pxlEnv.roomGlowPass.enabled=false;
          
          if( this.pxlEnv?.mapComposerGlow?.renderTarget2 ){
            this.pxlEnv.engine.setRenderTarget(this.pxlEnv.mapComposerGlow.renderTarget2);
            this.pxlEnv.engine.clear();
          }
          if( this.pxlEnv?.roomGlowPass?.renderTarget2 ){
            this.pxlEnv.engine.setRenderTarget(this.pxlEnv.roomGlowPass.renderTarget2);
            this.pxlEnv.engine.clear();
          }
          
          this.pxlEnv.engine.setRenderTarget(null);
                    
          this.pxlEnv.userScreenIntensity.x=.8;
          this.pxlEnv.userScreenIntensity.y=0;
                    
          //multVal=1.0;
          portalIntensity=1;
                    circleGateBloom=0;
                    circleGateColor=.5;
        }
                let cGate=this.pxlEnv.geoList['Circular_Gate'];
        if( cGate ){
                    cGate.material.uniforms.bloomBoost.value=circleGateBloom;
                }
                
                if( this.pxlEnv.geoList['HDRView'] ){
                    this.pxlEnv.geoList['HDRView'].material.uniforms.cdMult.value= this.settings.bloom ? .3 : .7;
                }
                
                let cVid=this.pxlEnv.geoList['CirculateGateVideoSphere'];
        if( cVid ){
                    cVid.material.color.r=circleGateColor;
                    cVid.material.color.g=circleGateColor;
                    cVid.material.color.b=circleGateColor;
                }
                
        if( this.pxlEnv.portaluserScreenIntensity ) this.pxlEnv.portaluserScreenIntensity.x=portalIntensity;
        //this.pxlEnv.pxlRenderSettings.mult=multVal;
        this.pxlEnv.pxlCamera.colliderCurObjHit=null;
        //this.pxlEnv.setExposure(multVal);
  }
  
};