pxlNav/core/Device.js


/**
 * @namespace pxlDevice
 * @description Device and input handling
 */

import { 
  Vector2,
  Vector3,
  Quaternion
} from "../../libs/three/three.module.min.js";

// TODO : So much dependency on outside data and classes
//          Promote as much as possible to callback subscriptions

export class Device{
  constructor(projectTitle, pxlCore, mobile){
    this.projectTitle=projectTitle;
    this.pxlCore=pxlCore;
    this.pxlEnv=null;
    this.pxlEnums=null;
    this.pxlTimer=null;
    this.pxlAudio=null;
    this.pxlUser=null;
    this.pxlCamera=null;
    this.pxlAutoCam=null;
    this.pxlGuiDraws=null;
    this.pxlQuality=null;
    this.pxlOptions=null;


    // If the cursor swaps between grabbing and grabable
    // 'false' will keep the cursor as a pointer
    // 'true' will be grabable over pxlNav, grabbing while clicking
    this.allowCursorSwap = false;

    this.longTouchLength = .75; // Seconds to hold a tap, without moving, for long touch
    
    //this.bootTime=new Date().getTime();

    let sW = window.innerWidth;
    let sH = window.innerHeight;
    this.sW=sW;
    this.sH=sH;

    this.lookRange = .30;
    this.lookXScalar = 1 / (this.sW * this.lookRange);
    this.lookYScalar = 1 / (this.sH * this.lookRange);
    this.touchScreen=false;
    this.x=sW*.5;
    this.y=sH*.5;
    this.screenRes=new Vector2( 1/sW,1/sH );
    this.screenAspect=new Vector2( 1,1);//sH/sW,sW/sH );
    this.screenRatio=new Vector2( sW/sH, sH/sW );
    this.origRes=new Vector2( sW, sH );
    this.screenResDeltaPerc=new Vector2( 1, 1 );
    this.mapW=1;
    this.mapH=1;
    //this.wheelDelta=0;
    
    this.gammaCorrection = new Vector3(1,1,1);
    this.lightShift = new Vector2(1,1);
    
    this.firefox=/Firefox/i.test(navigator.userAgent);
    this.mobile=mobile;
        
    //const IE = document.all?true:false;
    //this.mouseWheelEvt=(firefox)? "DOMMouseScroll" : "mousewheel" ;
    this.button=0;
    this.inputActive=false;
    this.startPos=[0,0];
    this.endPos=[0,0];
    this.dragCount=0;
    this.dragTotal=0;
    this.latched=false;
    this.windowHidden=false;
    
    // -- -- -- -- -- -- -- -- //
    
    this.mouseX=sW/2;
    this.mouseY=sH/2;

    this.keyDownCount=[0,0,0];
    this.directionKeyDown=false;
    this.directionKeysPressed=[0,0,0,0];
    this.controlKey=false;
    
    this.objectPercList=[];
    this.objectPercFuncList={};
    this.objectPerc={
        active:false,
        object:null,
        left:0,
        top:0,
        width:0,
        height:0,
        startX:0,
        startY:0,
        offsetX:0,
        offsetY:0,
        percX:0,
        percY:0,
        offsetPercX:0,
        offsetPercY:0,
    };
    this.canCursorLock=
      'pointerLockElement' in document||
      'mozPointerLockElement' in document ||
      'webkitPointerLockElement' in document;
    this.cursorLockActive=false;
    this.cursorRightButtonLockActive=false;
    
    // ## All of this isn't needed, clean it up
    //      Controller Module?
    this.gyroGravity=[0,0,0];
    this.touchMouseData={
      'active':false,
      'lock':false,
      'mode':0,
      'updated':0,
      'button':0,
      'dragCount':0,
      'dragTotal':0,
      'startPos':null, //vec2
      'moveMult':new Vector2(1.,1.),
      'velocityEase':new Vector2(0,0),
      'velocityEasePrev':null, // new Vector2(0,0)
      'velocity':new Vector2(0,0), //vec2
      'mBlurVelInf':new Vector2(2*this.screenRes.x,2*this.screenRes.y),
      'prevDeltaPos':[0,0,0], //vec2
      'endPos':null, //  [x,y] 
      'latchMatrix':null, // Mat4
      'netDistance':new Vector2(0,0), //vec2
      'netDistYPerc':0, //vec2
      'curDistance':new Vector2(0,0), //vec2
      'curFadeOut':new Vector2(0,0), //vec2
      'curStepDistance':new Vector2(0,0), //vec2
      'initialQuat':new Quaternion(),
      'releaseTime':0,
    };
        
    this.subscriptableEvents=[
        //"touchstart",
        //"touchmove",
        //"touchend",
        //"mousedown",
        //"mousemove",
        //"mouseup",
        "keydown",
        "keyup",
        "resize"
      ];
    this.callbackList={};
    
  }

  setDependencies( pxlNav ){
    this.pxlEnv=pxlNav.pxlEnv;
    this.pxlEnums=pxlNav.pxlEnums;
    this.pxlTimer=pxlNav.pxlTimer;
    this.pxlAudio=pxlNav.pxlAudio;
    this.pxlUser=pxlNav.pxlUser;
    this.pxlCamera=pxlNav.pxlCamera;
    this.pxlAutoCam=pxlNav.pxlAutoCam;
    this.pxlGuiDraws=pxlNav.pxlGuiDraws;
    this.pxlQuality=pxlNav.pxlQuality;
    this.pxlOptions=pxlNav.pxlOptions;
  }

  init(){
    
    this.setGammaCorrection();
    
    let curObj=this;
    if( !this.pxlOptions.staticCamera || (this.pxlOptions.allowStaticRotation && this.pxlOptions.staticCamera) ){
      document.addEventListener("mousedown", (e)=>{ curObj.onmousedown(curObj,e); }, false);
      document.addEventListener("mousemove", (e)=>{ curObj.onmousemove(curObj,e); }, false);
      document.addEventListener("mouseup", (e)=>{ curObj.onmouseup(curObj,e); }, false);
      document.addEventListener('touchstart', function(e) {curObj.touchstart(curObj,e);}, {passive : true});
      document.addEventListener('touchmove', function(e) {curObj.touchmove(curObj,e);}, {passive : true});
      document.addEventListener('touchend', function(e) {curObj.touchend(curObj,e);}, {passive : true});
    }

    document.addEventListener("contextmenu", (e)=>{ curObj.oncontextmenu(e); }, false);
    window.addEventListener("resize", (e)=>{ curObj.resizeRenderResolution(); }, false);
    
    document.onkeydown=(e)=>{curObj.onkeydown(curObj,e)};
    document.onkeyup=(e)=>{curObj.onkeyup(curObj,e)};
  
    let tmpThis=this;
    /*window.addEventListener("popstate", (e)=>{
        console.log( e );
        //JSON.stringify(e.state);
    });*/
    document.addEventListener("visibilitychange", this.onVisibilityChange.bind(this), false);
      
      if( typeof window.onblur == "object" ){
          window.onblur= this.resetUserInput.bind(this);
      }
        /*
    window.addEventListener( 'blur', (e)=>{
      tmpThis.directionKeysPressed=[0,0,0,0];
      tmpThis.directionKeyDown=false;
      tmpThis.pxlUser.setSpeed( tmpThis.pxlEnums.USER_SPEED.STOP );
      tmpThis.pxlCamera.workerFunc("focus");
    });*/
    window.addEventListener( 'beforeunload', (e)=>{
      if( tmpThis.controlKey==true ){
          e.preventDefault();
          e.returnValue="Close tab?";
          tmpThis.controlKey=false;
          tmpThis.mapLockCursor(false, 0);
          return "Close tab?";
      }
    });
  }
    
  // -- -- -- -- -- -- -- -- -- -- //

  step(){
    this.addMoveDelta();
  }

  // -- -- -- -- -- -- -- -- -- -- //
  
  // Post Process Gamma Correction; Set from Value / Detect based on OS
  // Default Gamma Settings --
  //   Windows - 2.2
  //   Mac - 1.8
  //   Linux - 1
  //     **Linux seems wrong, but can't find any reliable values other than `1`
  setGammaCorrection( value=null ){
    if( value != null ){ // Set from Settings Menu
      this.gammaCorrection.x = 1/value;
      this.gammaCorrection.y = value;
      this.gammaCorrection.z = value;
      return;
    }
    
    // Detect based on device operating system
    let toGamma = 1.5; // Most devices will be 1 (Mobile); Fail to middle ground
    let lightShift = 1.1; // Most devices will be 1 (Mobile); Fail to middle ground
    let shadowShift = 1.2; // Most devices will be 1 (Mobile); Fail to middle ground
    let shadowBoost = .5;
    if( window && window.navigator && window.navigator.userAgent ){
      let isWindows = window.navigator.userAgent.match(/(windows|win32|win64|wince)/i)
      if( isWindows && isWindows.length>0 ){ 
        toGamma = 2.2; // Windows
        lightShift = 0.95;
        shadowShift = 1;
        shadowBoost = 0;
      }else{
        let isMac = window.navigator.userAgent.match(/(macintosh|macintel|macppc|mac68k|iphone|ipad|ipod)/i)
        if( isMac && isMac.length>0 ){ 
          toGamma = 1.8; // Mac
          lightShift = 0.9;
          shadowShift = .97;
          shadowBoost = .1;
        }else{ 
          toGamma = 1.8; // Linux / Android
          lightShift = 0.70;
          shadowShift = .96;
          shadowBoost = .1;
        }
      }
    }
    // Color space is bugging me.... Just designing for windows and adjusting for the others
    this.gammaCorrection.x = 1/toGamma;
    this.gammaCorrection.y = shadowShift;
    this.gammaCorrection.z = shadowBoost;
    this.lightShift.x = lightShift;
  }
    
  // -- -- -- -- -- -- -- -- -- -- //
  
  getRenderScalar(){
    let renderScalar = 1.0;
    if( this.mobile && this.pxlOptions.renderScale.mobile != 0){
      renderScalar = this.pxlOptions.renderScale.mobile;
    }else if( this.pxlOptions.renderScale.pc != 0){
      renderScalar = this.pxlOptions.renderScale.pc;
    }

    return renderScalar;
  }

  // Set main pxlNav canvas size, with overscan if enabled
  //   Overscan is a percentage to render outside the canvas size
  //     But is scaled back to fit within the canvas
  //   This is primarily to allow for better rendering on mobile devices
  // Set `overscan` in pxlOptions to a percentage value
  //   pxlOptions.overscan = {
  //    'pc' : 0.0,
  //    'mobile' : 0.0
  //   }
  /**
   * Set main pxlNav canvas size, with overscan if enabled
   * 
   * Overscan is a percentage to render outside the canvas size
   * <br/>&nbsp;&nbsp; But is scaled back to fit within the canvas
   * <br/>This is primarily to allow for better rendering on mobile devices
   */
  setCanvasSize( sW, sH ){
    // Set main pxlNav Canvas Size
    this.pxlGuiDraws.pxlNavCanvas.width = sW;
    this.pxlGuiDraws.pxlNavCanvas.height = sH;
    
    let renderScalar = this.getRenderScalar();

    // Set overscan if enabled
    if( renderScalar != 1.0 ){
      let overscanFit = 1 / renderScalar;
      this.pxlGuiDraws.pxlNavCanvas.style.transform = "scale(" + overscanFit + ")";
    }else{
      this.pxlGuiDraws.pxlNavCanvas.style.transform = "";
    }
  }

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

  runHiddenCalcs(){
      if( this.windowHidden ){
          setTimeout( ()=>{
              this.runHiddenCalcs();
          }, 100);
      }
  }
  
  onVisibilityChange(){
    this.windowHidden=document.hidden ;
          
    this.directionKeysPressed=[0,0,0,0];
    this.directionKeyDown=false;
    this.pxlUser.setSpeed( this.pxlEnums.USER_SPEED.BASE );
    this.pxlCamera.workerFunc("focus", !document.hidden);
    
    this.runHiddenCalcs();
  }

  resetUserInput(e){
    this.directionKeysPressed=[0,0,0,0];
    this.directionKeyDown=false;
    this.pxlUser.setSpeed( this.pxlEnums.USER_SPEED.BASE );
    this.mapLockCursor(false,0);
    this.pxlCamera.camJumpKey(false);
    this.pxlCamera.deviceKey("space", false);
    
    if( this.touchMouseData.active ){
      if( this.touchScreen ){
        this.endTouch(e);
      }else{
        this.mapOnUp(e);
      }
    }
  }
  
  requestFullScreen(){
    let elem = document.documentElement;
    let requestMethod = elem.requestFullScreen ||
                        elem.webkitRequestFullScreen ||
                        elem.mozRequestFullScreen ||
                        elem.msRequestFullScreen;
    if( requestMethod ){
      requestMethod.call(elem);
    }
  }


  // -- -- -- -- -- -- -- -- -- -- //
  
  onmousemove(curObj,e){
    curObj.mapOnMove(e);
  }
  onmousedown(curObj,e){

    curObj.mapOnDown(e);
  }
  onmouseup(curObj,e){
    curObj.mapOnUp(e);
  }
  oncontextmenu(e){
        e.preventDefault();
        return false;
  }
  touchstart(curObj,e){
    curObj.startTouch(e);
  }
  touchmove(curObj,e){
    curObj.doTouch(e);
  }
  touchend(curObj,e){
    curObj.endTouch(e);
  }
  onkeydown(curObj,e){
    curObj.keyDownCall(e);
  }
  onkeyup(curObj,e){
    curObj.keyUpCall(e);
  }
  
  
  get active(){
    return this.inputActive;
  }
  set active(state){
    this.inputActive=!!state;
  }

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

  preventDefault(e){
    e=e||window.event;
    if (e.preventDefault(e)){
      e.preventDefault(e)();
    }
    e.returnValue=false;
  }
    
  setCursor(cursorType){
    if(this.allowCursorSwap){
      if(cursorType == "activeLatch"){
              let cursorType=["grab", "grabbing","all-scroll"][this.touchMouseData.button];
      }
      document.body.style.cursor=cursorType;
    }
  }

  hideAddressBar(){
    setTimeout(function(){ window.scrollTo(0, 1); }, 0);
  }
    
  getMouseXY(e){
    //e.this.preventDefault(e)();
    if(!this.mobile){
      let invert=this.pxlQuality.settings.mouse ? -1 : 1;
      if(this.cursorLockActive){
        this.mouseX+=(e.movementX||e.mozMovementX||e.webkitMovementX||0)*invert;
        this.mouseY+=(e.movementY||e.mozMovementY||e.webkitMovementY||0)*invert;
      }else{
        this.mouseX=e.clientX;
        this.mouseY=e.clientY;
      }
    }else{
      try{
        touch = e.touches[0];
        this.mouseX = touch.pageX;
        this.mouseY = touch.pageY;
        //pxlDevice.touchMouseData.startPos=new Vector2(this.mouseX,this.mouseY);
        //pxlDevice.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
      }catch(err){};
    }
    //mapMouse.x=(this.mouseX/sW)*2 - 1;
    //mapMouse.y=-(this.mouseY/sH)*2 + 1;
  }

  mapLockCursor(lock=false, button){
    if(this.canCursorLock && !this.mobile ){
      if(button==2){
        if(!lock && this.cursorRightButtonLockActive){
          this.cursorRightButtonLockActive=false;
        }else if(!lock && !this.cursorRightButtonLockActive){
          this.cursorRightButtonLockActive=true;
        }
        
        lock=lock || this.cursorRightButtonLockActive;
      }else{
        if(!lock){
          this.cursorRightButtonLockActive=lock;
        }
      }
      
      if(lock==true){
        this.pxlGuiDraws.pxlNavCanvas.focus();
        let curCanvas = this.pxlGuiDraws.pxlNavCanvas;
        curCanvas.requestPointerLock = curCanvas.requestPointerLock || 
                                       curCanvas.mozRequestPointerLock ||
                                       curCanvas.webkitRequestPointerLock;
        try{
          curCanvas.requestPointerLock()
          //  Catch isn't working on firefox
          //    .catch((err)=>{}); // User likely hit ESC out of the cursor lock
        }catch(err){}
      }else{
        if( document.pointerLockElement ){
          document.exitPointerLock();
        }
      }
      this.cursorLockActive=lock;
    }
  }

  mapOnDown(e){
    //let target= e.path ? e.path[0] : e.target; // Chrome or Firefox
    let target= e.target; // Chrome or Firefox
    if( target.getAttribute && target.getAttribute("id") == this.pxlCore && this.pxlTimer.active ){
      if(this.pxlGuiDraws.chatMessageInput){ this.pxlGuiDraws.chatMessageInput.blur(); }
      this.pxlGuiDraws.pxlNavCanvas.focus();
      this.preventDefault(e);
      this.touchMouseData.button=e.which;
      this.touchMouseData.active=true;
      this.touchMouseData.mode=this.touchMouseData.button;
      this.touchMouseData.startPos=new Vector2(this.mouseX,this.mouseY);
      this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
      this.touchMouseData.curDistance=new Vector2(0,0);
      this.touchMouseData.curStepDistance=new Vector2(0,0);
      this.touchMouseData.dragCount=0;
      this.pxlAutoCam.touchBlender=false;
      this.setCursor("grabbing");
      this.mapLockCursor(true, e.button);
    }
  }
  mapOnMove(e){
    //let target= e.path ? e.path[0] : e.target; // Chrome or Firefox
    let target= e.target; // Chrome or Firefox

    if( (target.getAttribute && target.getAttribute("id") == this.pxlCore && this.pxlTimer.active) || this.cursorLockActive){

      this.preventDefault(e);
      this.getMouseXY(e);
      if((this.touchMouseData.active || this.cursorLockActive) && this.touchMouseData.startPos ){
        this.touchMouseData.dragCount++;

        let xyDeltaTemp=this.touchMouseData.endPos.clone();
        this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
        this.touchMouseData.curDistance= this.touchMouseData.startPos.clone().sub(this.touchMouseData.endPos) ;
        
        this.touchMouseData.curStepDistance = this.touchMouseData.endPos.clone().sub(xyDeltaTemp) ;
        this.touchMouseData.netDistance.add( this.touchMouseData.curStepDistance.clone());
        //let curvelocity=this.touchMouseData.velocity.clone();
        this.touchMouseData.velocity = this.touchMouseData.velocity.clone().multiplyScalar(3).add(this.touchMouseData.curStepDistance).multiplyScalar(.25);

        // ## Add into this.pxlQuality for 3rd mouse velocity reference
        /*this.touchMouseData.velocity.add(this.touchMouseData.curStepDistance).multiplyScalar(.5);
        let curveleaseprev=this.touchMouseData.velocityEasePrev.clone();
        this.touchMouseData.velocityEasePrev=this.touchMouseData.velocityEase.clone();
        this.touchMouseData.velocityEase=curveleaseprev.clone().add(curvelocity.add(this.touchMouseData.velocity).multiplyScalar(.5)).multiplyScalar(.5);*/
        
        this.touchMouseData.netDistYPerc = (this.touchMouseData.netDistance.y+this.touchMouseData.curDistance.y+250)*0.0008;
        this.touchMouseData.curFadeOut.add( xyDeltaTemp.sub(this.touchMouseData.endPos)  );
        
      }else{
        this.pxlEnv.hoverUserDetect();
      }
    }
  }
  mapOnUp(e){
    //let target= e.path ? e.path[0] : e.target; // Chrome or Firefox
    let target= e.target; // Chrome or Firefox
        if( this.pxlAudio.djVolumeParentObj ){
            this.pxlAudio.djVolumeParentObj.down=false;
        }
        if( target.getAttribute && target.getAttribute("id") == this.pxlCore){
            this.pxlGuiDraws.checkFocus( target.getAttribute("id"), true );
            
            if( this.mobile ){
                this.pxlAutoCam.getNextPath(false, 1);
            }else{
                
                this.preventDefault(e);
                
                this.touchMouseData.dragCount++;
                this.touchMouseData.dragTotal+=this.touchMouseData.dragCount;
                this.touchMouseData.active=false;
                this.touchMouseData.releaseTime=this.pxlTimer.curMS;
                this.pxlAutoCam.touchBlender=true;
                this.pxlAutoCam.setNextTrigger();
                
                if(this.touchMouseData.dragCount<9){ // User simply clicked, didn't dragCount
                    this.pxlEnv.clickUserDetect();
                }
                this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
                this.touchMouseData.netDistYPerc =  (this.touchMouseData.netDistance.y+250)/1250;
                //this.touchMouseData.curDistance.multiplyScalar(0);
                //this.touchMouseData.curStepDistance.multiplyScalar(0);
                this.setCursor("grab");
                
                this.mapLockCursor(false, e.button);
                
                e.preventDefault();
                return false;
            }
        }
  }

    // -- -- -- -- -- -- //
    
  startTouch(e) {
    
    let target= e.target; // Chrome or Firefox
    // Check if something other than the pxlCore canvas was touched
    let runTouch = target.getAttribute && target.getAttribute("id") == this.pxlCore && this.pxlTimer.active;
    if( !runTouch ){
      return;
    }

    this.touchScreen=true;
    let touch = e.touches[0];
    this.mouseX = touch.pageX;  
    this.mouseY = touch.pageY;
    this.touchMouseData.active=true;
    this.touchMouseData.mode=this.touchMouseData.button;
    this.touchMouseData.startPos=new Vector2(this.mouseX,this.mouseY);
    this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
    this.touchMouseData.curDistance=new Vector2(0,0);
    this.touchMouseData.curStepDistance=new Vector2(0,0);
    this.touchMouseData.dragCount=0;
    this.pxlAutoCam.touchBlender=false;
    this.touchMouseData.releaseTime=this.pxlTimer.curMS;
    
    let id=target.getAttribute("id");
    if( this.objectPercList.includes( id ) ){
      if( id == "djPlayerVolume" ){
        target=this.pxlAudio.djVolumeParentObj;
        id=target.getAttribute("id");
      }
      
      let pBB=target.getBoundingClientRect();

      this.objectPerc.active=true;
      this.objectPerc.object=target;
      this.objectPerc.left=pBB.left;
      this.objectPerc.top=pBB.top;
      this.objectPerc.width=pBB.width;
      this.objectPerc.height=pBB.height;
      this.objectPerc.startX= this.mouseX - pBB.left;
      this.objectPerc.startY= this.mouseY - pBB.top;
      this.objectPerc.percX= ( this.mouseX - this.objectPerc.left ) / this.objectPerc.width ;
      this.objectPerc.percY= ( this.mouseY -this.objectPerc.top ) / this.objectPerc.height ;
      this.objectPerc.offsetX=0;
      this.objectPerc.offsetY=0;
      this.objectPerc.offsetPercX = 0 ;
      this.objectPerc.offsetPercY = 0 ;
      
      if( this.objectPercFuncList[id] ){
        this.objectPercFuncList[id](e);
      }
    }
  }

  doTouch(e) {
    
    // Touch started on something other than the pxlCore canvas, ignore
    if(!this.touchMouseData.active){
      return;
    }
    
    let target= e.target; // Chrome or Firefox

    let touch = e.touches[0];
    this.mouseX=touch.pageX;
    this.mouseY=touch.pageY;
    if(this.touchMouseData.active){
      this.touchMouseData.dragCount++;
      
      // Rotate the fog color, cause why not???
      // TODO : "why not???" because its specific to Antibody Club,
      //          But its fun, so I'll figure out some way to make it a bit more generic if the user wants to enable it
      if(typeof(e.touches[1]) !== 'undefined'){
        let touchTwo = e.touches[1];
        if( e.touches.length>1 && this.touchMouseData.dragCount>10 ){
          this.touchMouseData.lock=true;
          touch = e.touches[1];
          this.pxlEnv.setFogHue( [this.mouseX, this.mouseY], [touch.pageX, touch.pageY] );
        }
        return;
      }

      let xyDeltaTemp=this.touchMouseData.endPos.clone();
      this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
      this.touchMouseData.curDistance= this.touchMouseData.startPos.clone().sub(this.touchMouseData.endPos) ;
      this.touchMouseData.curStepDistance = this.touchMouseData.endPos.clone().sub(xyDeltaTemp) ;
      this.touchMouseData.netDistance.add( this.touchMouseData.curStepDistance.clone() );
      this.touchMouseData.velocity.add(this.touchMouseData.curStepDistance).multiplyScalar(.5);
      //this.touchMouseData.velocityEase.add(this.touchMouseData.curStepDistance).multiplyScalar(.5);
            
      this.touchMouseData.netDistYPerc =  (this.touchMouseData.netDistance.y+this.touchMouseData.curDistance.y+250)/1250;
      this.touchMouseData.curFadeOut.add( xyDeltaTemp.sub(this.touchMouseData.endPos)  );
    }
      
    if( this.objectPerc.active ){
      this.objectPerc.percX = ( this.mouseX - this.objectPerc.left ) / this.objectPerc.width ;
      this.objectPerc.percY = ( this.mouseY - this.objectPerc.top ) / this.objectPerc.height ;
      this.objectPerc.offsetX = this.mouseX - this.objectPerc.startX ;
      this.objectPerc.offsetY = this.mouseY - this.objectPerc.startY ;
      this.objectPerc.offsetPercX = this.objectPerc.offsetX / this.objectPerc.width ;
      this.objectPerc.offsetPercY = this.objectPerc.offsetY / this.objectPerc.height ;
    }
        
  }
  endTouch(e) {

    // Touch started on something other than the pxlCore canvas, ignore
    if(!this.touchMouseData.active){
      return;
    }

    this.touchScreen=false;
    this.touchMouseData.dragCount++;
    this.touchMouseData.dragTotal+=this.touchMouseData.dragCount;
    this.touchMouseData.active=false;
    this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
    //this.touchMouseData.netDistance.add( this.touchMouseData.curDistance.clone() );
    //this.touchMouseData.netDistance.y = Math.max(-1000, Math.min(1500, this.touchMouseData.netDistance.y ));
    this.touchMouseData.netDistYPerc =  (this.touchMouseData.netDistance.y+250)/1250;
    this.touchMouseData.curDistance.multiplyScalar(0);
    this.touchMouseData.curStepDistance.multiplyScalar(0);
        
    /*
      // Disabling for now, but is the overlay effect trigger when holding a long-touch
      if( this.mobile && this.touchMouseData.dragCount<10 && this.pxlTimer.curMS-this.touchMouseData.releaseTime > this.longTouchLength ){
        this.pxlCamera.itemTrigger();
        this.touchMouseData.lock=true;
      }
        */
      this.touchMouseData.releaseTime=this.pxlTimer.curMS;
      
      if( this.touchMouseData.lock ){
          this.touchMouseData.lock=false;
          return;
      }
      
      this.pxlAutoCam.touchBlender=true;
      this.pxlAutoCam.setNextTrigger();
        
    //let target= e.path ? e.path[0] : e.target; // Chrome or Firefox
    /*let target= e.target; // Chrome or Firefox
      //let dragLength=this.touchMouseData.startPos.clone().sub( this.touchMouseData.endPos ).length();
      if( this.touchMouseData.dragCount < 10 && target.getAttribute && target.getAttribute("id")==this.pxlCore ){
          // this.pxlAutoCam.prevNextAutoCam(1, true);
          this.pxlAutoCam.getNextPath(false, 0);
      }
      
      this.objectPerc.active=false;
      if( this.pxlAudio.djVolumeParentObj ){
          this.pxlAudio.djVolumeParentObj.down=false;
      }*/
  }
    
    // -- -- -- -- -- -- //
    
  async keyDownCall(e){

    this.emit("keydown", e);

    //e.this.preventDefault(e)();
        if( e.ctrlKey ){
            this.controlKey=true;
        }
    if(document.activeElement.type==undefined ){
      //%=
      if(false){
      //%
      if( e.ctrlKey || e.altKey || e.code.includes("F") ){
          e.preventDefault();
          return false;
      }
      //%=
      }
      //%
      
      if( this.pxlTimer.active){
        if( e.repeat ){
          return;
        }

        let keyHit=e.keyCode || e.which;
        if(keyHit==37 || keyHit==65){ // Left
          this.directionKeyDown=true;
          this.keyDownCount[0]=this.pxlQuality.fpsCounter.z;
          this.directionKeysPressed[0]=1;
          this.pxlCamera.deviceKey(0, true);
        }
        if(e.ctrlKey && keyHit==87 && this.directionKeysPressed[1]==1){
          e.this.preventDefault(e)();
        }
        if(keyHit==38 || keyHit==87){ // Up
          this.directionKeyDown=true;
          this.keyDownCount[1]=this.pxlQuality.fpsCounter.z;
          this.directionKeysPressed[1]=1;
          this.pxlCamera.deviceKey(1, true);
        }
        if(keyHit==39 || keyHit==68){ // Right
          this.directionKeyDown=true;
          this.keyDownCount[0]=this.pxlQuality.fpsCounter.z;
          this.directionKeysPressed[2]=1;

          this.pxlCamera.deviceKey(2, true);
        }
        if(keyHit==40 || keyHit==83){ // Down
          this.directionKeyDown=true;
          this.keyDownCount[1]=this.pxlQuality.fpsCounter.z;
          this.directionKeysPressed[3]=1;
          this.pxlCamera.deviceKey(3, true);
        }
        if(keyHit==16 || keyHit==224){ // Shift
          this.deviceAction( this.pxlEnums.DEVICE_ACTION.RUN, {}, true );
        }
        if(keyHit==32){
          this.deviceAction( this.pxlEnums.DEVICE_ACTION.JUMP, {}, true ); 
        }
      }
    }//else{
      // pxlNav Canvas not in focus
      //   User likely typing within a text field or other focus'ed object 
    //}
  }
    

  async keyUpCall(e){

    this.emit("keyup", e);

    //e.this.preventDefault(e)();
        if( e.ctrlKey ){
            this.controlKey=false;
            e.preventDefault();
            return false;
        }
    if(document.activeElement.type==undefined ){
      let keyHit=e.keyCode || e.which;
      let kCode=e.code || e.which;
      if( !e.isTrusted ){
        return false;
      }
      if( e.ctrlKey || e.altKey || e.code.includes("F") ){
          e.preventDefault();
          return false;
      }
            
      // -- -- -- -- -- -- -- -- -- -- //

      // Non-Active dependent functions -
      // 192 `
      if( kCode == "Backquote" ){
        this.pxlGuiDraws.guiToggleVisibility(); // No Texture
        return;
      }
      // 89 Y
      if( keyHit == 89 ){
        this.pxlGuiDraws.toggleShaderEditor();
      }
              
      // 220 \ | 
      if( keyHit == 220 ){
        //console.log( "Saving screenshot" );
        let tmpResSave=this.pxlQuality.screenResPerc;
        this.pxlQuality.screenResPerc=1;
        //this.resizeRenderResolution( 3840, 2160 );//3240, 3240 );
        
        this.pxlEnv.mapRender(false);
        
        let dlData=this.pxlGuiDraws.pxlNavCanvas.toDataURL("image/png");
        let blob=atob( dlData.split(',')[1] );
        let size=blob.length;
        let arr=new Uint8Array(size);
        for(let x=0; x<size; ++x){
            arr[x]=blob.charCodeAt(x);
        }
        let cameraData=URL.createObjectURL(new Blob([arr]));

        let dt=new Date();
        let d=dt.getDate();
        let m=dt.getMonth()+1;
        let y=dt.getFullYear();
        let fileSuffix=y+"-"+m+"-"+d+"_"+dt.getHours()+"-"+dt.getMinutes()+"-"+dt.getSeconds();

        let tempLink=document.createElement("a");
        tempLink.download=this.projectTitle+"_"+fileSuffix+".png";
        tempLink.href=cameraData;
        document.body.appendChild(tempLink);
        tempLink.click();
        tempLink.parentNode.removeChild(tempLink);
        
        this.pxlQuality.screenResPerc=tmpResSave;
        //this.resizeRenderResolution();
        this.pxlEnv.mapRender(false);
        
        return;
      }

      // -- -- -- -- -- -- -- -- -- -- //
      
      // Active dependent functions; pxlNav needs to be running

      if(this.pxlTimer.active){
        if(keyHit==37 || keyHit==65){ // Left
          this.directionKeysPressed[0]=0;
          //this.pxlAutoCam.prevNextAutoCam(-1);
          //this.pxlAutoCam.getNextPath(false, -1);
          this.pxlCamera.deviceKey(0, false);
        }
        if(keyHit==38 || keyHit==87){ // Up
          this.directionKeysPressed[1]=0;
          if( this.pxlAutoCam.active ){
              this.pxlAutoCam.step(true);
          }
          this.pxlCamera.deviceKey(1, false);
        }
        if(keyHit==39 || keyHit==68){ // Right
          this.directionKeysPressed[2]=0;
          this.pxlCamera.deviceKey(2, false);
        }
        if(keyHit==40 || keyHit==83){ // Down
          this.directionKeysPressed[3]=0;
          if( this.pxlAutoCam.active ){
              this.pxlAutoCam.setRoom(true);
          }
          this.pxlCamera.deviceKey(3, false);
        }
        if(!this.directionKeysPressed.includes(1)){
          this.directionKeyDown=false;
        }
        // Shift
        if(keyHit==16 || keyHit==224){ // Shift
          this.deviceAction( this.pxlEnums.DEVICE_ACTION.RUN, {}, false );
          return;
        }
        // Space
        if(keyHit==32){
          this.deviceAction( this.pxlEnums.DEVICE_ACTION.JUMP, {}, false );
          return;
        }
        
        if( !this.directionKeyDown ){
          // 1 / Numpad 1 - Warp to Lobby
          /*if(keyHit == 49 || keyHit == 97){
            this.pxlCamera.fastTravel(0);
            return;
          }*/
          // 5 / Numpad 5 - Drone Cam
          if(keyHit == 53 || keyHit == 101){
            this.pxlAutoCam.preAutoCamToggle();
            return;
          }
        }
                
             
        
        //%=
        // 75 K Numpad-Plus
        if(keyHit == 75 || keyHit == 107 || keyHit == 187){
        }
        // 74 J Numpad-Minus
        if(keyHit == 74 || keyHit == 109 || keyHit == 189){
        }
        // 76 L
        if(keyHit == 76){
        }
        // 48  0
        if(keyHit == 48){
        }
        
        // 221 ]
        if( keyHit == 221 ){
          // Prevent current item from wearing off
          //   Printing it from the check list
          if( this.pxlUser?.itemInactiveCmd?.length >0 ){
            this.pxlUser.itemInactiveCmd.pop();
          }
          return;
        }
        // 106 *
        if( keyHit == 106 ){
        }
        
      }
      
      // Close all gui windows
      // ESC / Enter
      if(keyHit==27 || ( keyHit == 13 && !e.ctrlKey )){
        this.pxlGuiDraws.toggleHudBlock(false, true);
        this.pxlGuiDraws.toggleGuiWindowContainer(false, false, true);
        return;
      }
      
      
      if( e.altKey || e.ctrlKey || e.shiftKey ){
        return;
      }
      
      // 85  U
      if(keyHit == 85){
      }
      // 73  I
      if(keyHit == 73){
        this.pxlGuiDraws.iconEvent( "click", this.pxlGuiDraws.hudIcons.infoIcon, "info" );
        return;
      }
      // 71 G
      if(keyHit == 71){
        this.pxlGuiDraws.iconEvent( "click", this.pxlGuiDraws.hudIcons.settingsIcon, "settings" );
        return;
      }
      // 72 H; 191  ?
      if( keyHit == 191 || keyHit == 72 ){ // Open Help Screen
        this.pxlGuiDraws.iconEvent( "click", this.pxlGuiDraws.hudIcons.helpIcon, "help" );
        return;
      }
      
      // 67  C
      if(keyHit == 67){
      }
      // 66  B
      if(keyHit == 66){
        this.pxlGuiDraws.iconEvent( "click", this.pxlGuiDraws.hudIcons.musicIcon, "musicToggle" );
        return;
      }
      // 78 N
      if(keyHit == 78){
        this.pxlGuiDraws.iconEvent( "click", this.pxlGuiDraws.hudIcons.speakerIcon, "speakerToggle" );
        return;
      }
      // 77  M
      if(keyHit == 77){
      }
      // 86  V
      if(keyHit == 86){
      }
      
      // P 
      if(keyHit == 80){ // Pause pxlNav Environment
        this.deviceAction( this.pxlEnums.DEVICE_ACTION.PAUSE );
        return;
      }
    }
  }

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


  deviceAction( action, data, state=null ){
    switch( action ){
      case this.pxlEnums.DEVICE_ACTION.MOVE:
        //this.directionKeysPressed[2]=0;
        this.addMoveDelta( data );
        //this.pxlCamera.deviceKey(1, state);
        break;
      case this.pxlEnums.DEVICE_ACTION.LOOK:
        this.addLookDelta( data );
        //this.pxlCamera.deviceKey(2, state);
        break;
      case this.pxlEnums.DEVICE_ACTION.JUMP:
        this.pxlCamera.camJumpKey( state );
        //this.pxlCamera.deviceKey("space", state);
        break;
      case this.pxlEnums.DEVICE_ACTION.RUN:
        let speed = state ? this.pxlEnums.USER_SPEED.BOOST : this.pxlEnums.USER_SPEED.BASE;
        this.pxlUser.setSpeed( speed );
        //this.pxlCamera.deviceKey("shift", true);
        break;
      case this.pxlEnums.DEVICE_ACTION.ACTION:
        //this.pxlCamera.deviceKey("action", state);
        break;
      case this.pxlEnums.DEVICE_ACTION.ACTION_ALT:
        //this.pxlCamera.deviceKey("actionAlt", state);
        break;
      case this.pxlEnums.DEVICE_ACTION.ITEM:
        //this.pxlCamera.deviceKey("item", state);
        break;
      case this.pxlEnums.DEVICE_ACTION.MENU:
        //this.pxlCamera.deviceKey("menu", state);
        break;
      case this.pxlEnums.DEVICE_ACTION.PAUSE:
        this.togglePause( state );
        //this.pxlCamera.deviceKey("pause", state);
        break;
      case this.pxlEnums.DEVICE_ACTION.MAP:
        //this.pxlCamera.deviceKey("map", state);
        break;
    }
  }


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

  addMoveDelta( data=null ){
    let status = false;
    if( !this.pxlOptions.mobile || this.pxlOptions.staticCamera || (data === null && !this.directionKeyDown)){
      return;
    }
    
    if( data !== null ){
      if( data.hasOwnProperty("status") ){
        status = data.status;
      }

      if( status ){
        let absDeltaX = Math.abs(data.startDelta.x);
        let absDeltaY = Math.abs(data.startDelta.y);

        let deadZone = this.pxlOptions.userSettings.deadZone.touch;
        let startDeltaX = absDeltaX > deadZone ? data.startDelta.x - deadZone*Math.sign(data.startDelta.x) : 0;
        let startDeltaY = absDeltaY > deadZone ? data.startDelta.y - deadZone*Math.sign(data.startDelta.y) : 0;

        // Fit pixel distances to percentage of screen
        let xRatio = this.sW / this.sH;
        let yRatio = this.sH / this.sW;
        startDeltaX = Math.min(startDeltaX * this.lookXScalar, 1.0) * this.pxlOptions.userSettings.movement.max * xRatio;
        startDeltaY = Math.min(startDeltaY * this.lookYScalar, 1.0) * this.pxlOptions.userSettings.movement.max * yRatio;

        //this.pxlCamera.cameraMovement[0] =  ( this.pxlCamera.cameraMovement[0] + Math.min( Math.max( startDeltaX * 0.01, -1), 1) ) * .5; // x / 50
        //this.pxlCamera.cameraMovement[1] = ( this.pxlCamera.cameraMovement[1] + Math.min( Math.max( startDeltaY * 0.01, -1), 1) ) * .5; // x / 50 

        this.pxlCamera.cameraMovement[0] =  ( this.pxlCamera.cameraMovement[0] + startDeltaX ) * .7 // x / 50
        this.pxlCamera.cameraMovement[1] = ( this.pxlCamera.cameraMovement[1] +  startDeltaY ) * .7; // y / 50 
        this.userInputMoveScalar = Math.max(this.pxlCamera.cameraMovement[0]**2, this.pxlCamera.cameraMovement[1]**2) ** .5;
        this.userInputMoveScalar = this.userInputMoveScalar*this.userInputMoveScalar*this.userInputMoveScalar * 1.5;
        this.userInputMoveScalar += this.userInputMoveScalar>1.0 ? (this.userInputMoveScalar-1.0)*30. : 0;
        if( this.userInputMoveScalar > 1.0 ){
          // Trigger Run automatically on mobile
          this.deviceAction( this.pxlEnums.DEVICE_ACTION.RUN, {}, true );
        }else{
          this.deviceAction( this.pxlEnums.DEVICE_ACTION.RUN, {}, false );
        }
      }
    }else{
      status = this.directionKeyDown;
    }

    this.directionKeyDown = status;
    if( status ){
      this.directionKeysPressed[0]= this.pxlCamera.cameraMovement[0] < 0 ? 1 : 0;
      this.directionKeysPressed[1]= this.pxlCamera.cameraMovement[1] < 0 ? 1 : 0;
      this.directionKeysPressed[2]= this.pxlCamera.cameraMovement[0] > 0 ? 1 : 0;
      this.directionKeysPressed[3]= this.pxlCamera.cameraMovement[1] > 0 ? 1 : 0;
      this.pxlCamera.userInputMoveScalar.x = this.pxlCamera.cameraMovement[0];
      this.pxlCamera.userInputMoveScalar.y = this.pxlCamera.cameraMovement[1];
      this.pxlCamera.cameraMovementEase = 1;
      this.pxlUser.setSpeed( this.userInputMoveScalar * this.pxlEnums.USER_SPEED.BASE );
    }else{
      this.directionKeysPressed[0]=0;
      this.directionKeysPressed[1]=0;
      this.directionKeysPressed[2]=0;
      this.directionKeysPressed[3]=0;
      this.pxlCamera.cameraMovementEase = .85;
    }
  }

  addLookDelta( data ){
    let status = false;
    if( data.hasOwnProperty("status") ){
      status = data.status;
    }

    this.mouseX = data.currentPos.x;
    this.mouseY = data.currentPos.y;

    if( status ){

      if( data.dragCount == 0 ){
        this.touchScreen=true;
        this.touchMouseData.active=true;
        this.touchMouseData.mode=this.touchMouseData.button;
        this.touchMouseData.startPos=new Vector2(this.mouseX,this.mouseY);
        this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
        this.touchMouseData.curDistance=new Vector2(0,0);
        this.touchMouseData.curStepDistance=new Vector2(0,0);
        this.touchMouseData.dragCount=0;
        this.pxlAutoCam.touchBlender=false;
        this.touchMouseData.releaseTime=this.pxlTimer.curMS;
      }else{
        this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);

        this.touchMouseData.curDistance= new Vector2( data.startDelta.x, data.startDelta.y );
        this.touchMouseData.curStepDistance = new Vector2( data.stepDelta.x, data.stepDelta.y );
        this.touchMouseData.netDistance.add( this.touchMouseData.curStepDistance.clone() );
        this.touchMouseData.velocity.add(this.touchMouseData.curStepDistance).multiplyScalar(.5);
        //this.touchMouseData.velocityEase.add(this.touchMouseData.curStepDistance).multiplyScalar(.5);
              
        this.touchMouseData.netDistYPerc =  (this.touchMouseData.netDistance.y+this.touchMouseData.curDistance.y+250)/1250;
        
        this.touchMouseData.curFadeOut.add( (new Vector2( data.previousPos.x, data.previousPos.y )).sub(this.touchMouseData.endPos)  );
  
          
        if( this.objectPerc.active ){
          this.objectPerc.percX = ( this.mouseX - this.objectPerc.left ) / this.objectPerc.width ;
          this.objectPerc.percY = ( this.mouseY - this.objectPerc.top ) / this.objectPerc.height ;
          this.objectPerc.offsetX = this.mouseX - this.objectPerc.startX ;
          this.objectPerc.offsetY = this.mouseY - this.objectPerc.startY ;
          this.objectPerc.offsetPercX = this.objectPerc.offsetX / this.objectPerc.width ;
          this.objectPerc.offsetPercY = this.objectPerc.offsetY / this.objectPerc.height ;
        }
      }

    }else{
        
      this.touchScreen=false;
      this.touchMouseData.dragTotal+=data.dragCount;
      this.touchMouseData.active=false;
      this.touchMouseData.endPos=new Vector2(this.mouseX,this.mouseY);
      //this.touchMouseData.netDistance.add( this.touchMouseData.curDistance.clone() );
      //this.touchMouseData.netDistance.y = Math.max(-1000, Math.min(1500, this.touchMouseData.netDistance.y ));
      this.touchMouseData.netDistYPerc =  (this.touchMouseData.netDistance.y+250)/1250;
      this.touchMouseData.curDistance.multiplyScalar(0);
      this.touchMouseData.curStepDistance.multiplyScalar(0);

      this.touchMouseData.releaseTime=this.pxlTimer.curMS;
      
      if( this.touchMouseData.lock ){
          this.touchMouseData.lock=false;
          return;
      }
      
      this.pxlAutoCam.touchBlender=true;
      //this.pxlAutoCam.setNextTrigger();
    }
  }

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

  togglePause( state=null ){
    if( state === null ){
      state = !this.pxlTimer.active;
    }
    this.directionKeysPressed=[0,0,0,0];
    this.directionKeyDown=false;
    this.pxlTimer.pause( state );
    if( this.pxlTimer.active ){ 
      this.pxlEnv.mapRender();
    }
    this.pxlCamera.workerFunc("activeToggle",this.pxlTimer.active);
  }


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

  // ## Have it run a pxlEnv class function instead of all this mess
  resizeRenderResolution( iWidthBase=null, iHeightBase=null ){

    //let iWidth= window?.screen?.width ? window.screen.width / window.devicePixelRatio : window.innerWidth;
    //let iHeight= window?.screen?.height ? window.screen.height / window.devicePixelRatio : window.innerHeight;

    let iWidth = window.innerWidth;
    let iHeight = window.innerHeight;
    iWidth=!iWidthBase ? iWidth : iWidthBase;
    iHeight=!iHeightBase ? iHeight : iHeightBase;

    // -- -- -- 

    let renderScalar = this.getRenderScalar();
    iWidth = iWidth * renderScalar;
    iHeight = iHeight * renderScalar;
        
    this.mapW=(this.sW=iWidth)*this.pxlQuality.screenResPerc;
    this.mapH=(this.sH=iHeight)*this.pxlQuality.screenResPerc;
        
    /*let screenWidth = window?.screen?.width ? window.screen.width : this.mapW;
    let screenHeight = window?.screen?.height ? window.screen.height : this.mapH;
    this.mapW=screenWidth;
    this.mapH=screenHeight;*/

    this.screenRes.x=1/this.mapW;
    this.screenRes.y=1/this.mapH;
    this.screenRatio.x=this.sW * this.screenRes.x;
    this.screenRatio.y=this.sH * this.screenRes.y;

    if(this.pxlEnv.geoList['HDRView']){
      let rU=this.mapW>this.mapH ? 1 : this.mapW/this.mapH;
      //let rV=this.mapW>this.mapH ? this.mapH  his.mapW : 1;
      this.pxlEnv.geoList['HDRView'].material.uniforms.ratioU.value=rU;
      //this.pxlEnv.geoList['HDRView'].material.uniforms.ratioV.value=rV;
    }
    
    this.touchMouseData.mBlurVelInf=new Vector2(2*this.screenRes.x,2*this.screenRes.y);
    if(!this.pxlEnv.mapGlowPass){
      return;
    }

    // TODO : All of this should be set up through callbacks vv
    //this.pxlEnv.scene.renderTarget.setSize(this.mapW*this.pxlQuality.bufferPercMult,this.mapH*this.pxlQuality.bufferPercMult);
    //this.pxlEnv.scene.renderWorldPos.setSize(this.mapW*this.pxlQuality.bufferPercMult,this.mapH*this.pxlQuality.bufferPercMult);
    
    this.pxlEnv.scene.renderTarget.setSize(this.mapW,this.mapH);
    this.pxlEnv.scene.renderWorldPos.setSize(this.mapW,this.mapH);
    
    if( this.pxlEnv.mapComposer ) this.pxlEnv.mapComposer.setSize(this.mapW,this.mapH);
    if( this.pxlEnv.mapComposerGlow ) this.pxlEnv.mapComposerGlow.setSize(this.mapW,this.mapH);
    
    // For external rooms --
    if( this.pxlEnv.roomComposer ){
      this.pxlEnv.roomComposer.setSize( this.mapW,this.mapH);
    }
  
    if( this.pxlEnv.roomGlowPass ){
      this.pxlEnv.roomGlowPass.setSize(this.mapW*this.pxlQuality.bloomPercMult,this.mapH*this.pxlQuality.bloomPercMult);
    }

    // -- -- -- -- -- -- --
        
    // For texture swapping --
    if( this.pxlEnv.delayComposer ) this.pxlEnv.delayComposer.setSize(this.mapW,this.mapH);

    // -- -- -- -- -- -- --
        
    if( this.pxlEnv.mapGlowPass ){
      this.pxlEnv.mapGlowPass.setSize(this.mapW*this.pxlQuality.bloomPercMult,this.mapH*this.pxlQuality.bloomPercMult);
    }
    
    if( this.pxlEnv.mapMotionBlurPass ){
      this.pxlEnv.mapMotionBlurPass.setSize(this.mapW*this.pxlQuality.mBlurPercMult,this.mapH*this.pxlQuality.mBlurPercMult);
    }
    
    if( this.pxlEnv.mapOverlayHeavyPass ){
      this.pxlEnv.mapOverlayHeavyPass.setSize(this.mapW,this.mapH);
      this.pxlEnv.mapOverlayHeavyPass.uniforms.ratio.value = Math.min( 1, this.mapW/this.mapH );
    }
    
    if( this.pxlEnv.mapOverlayPass ){
      this.pxlEnv.mapOverlayPass.setSize(this.mapW,this.mapH);
      this.pxlEnv.mapOverlayPass.uniforms.ratio.value = Math.min( 1, this.mapW/this.mapH );
    }

    if( this.pxlEnv.mapOverlaySlimPass ){
      this.pxlEnv.mapOverlaySlimPass.setSize(this.mapW,this.mapH);
      this.pxlEnv.mapOverlaySlimPass.uniforms.ratio.value = Math.min( 1, this.mapW/this.mapH );
    }
    

    // Set main pxlNav canvas size
    this.setCanvasSize( this.sW, this.sH );


    this.pxlGuiDraws.loading=true;
    
    this.pxlEnv.engine.setPixelRatio(window.devicePixelRatio*this.pxlQuality.screenResPerc);
    //this.pxlEnv.engine.setPixelRatio(window.devicePixelRatio);
    //this.pxlEnv.engine.setSize(this.mapW, this.mapH);
    this.pxlEnv.engine.setSize(this.sW, this.sH);
    //let aspectRatio=this.mapW/this.mapH;
    let aspectRatio = window.innerWidth / window.innerHeight;
    this.pxlCamera.setAspect( aspectRatio );
        
        
    let safeAspect=[ this.sW/this.sH, this.sH/this.sW ];
    let aspectMult=[1,1];
    aspectMult[0]=(aspectRatio>safeAspect[0]) ? 1 : this.sW/(this.sH*safeAspect[0]) ;
    aspectMult[1]=(aspectRatio>safeAspect[1]) ? this.sH/(this.sW*safeAspect[1]) : 1 ;
    if(aspectMult[0]>1){
      aspectMult[1]*=1/aspectMult[0];
      aspectMult[0]=1;
    }else if(aspectMult[1]>1){
      aspectMult[0]*=1/aspectMult[1];
      aspectMult[1]=1;
    }

    this.screenAspect.x=aspectMult[0] * (1/(.5**2+.5**2)**.5);
    this.screenAspect.y=aspectMult[1];
    
    this.screenResDeltaPerc.x=this.sW/this.origRes.x;
    this.screenResDeltaPerc.y=this.sH/this.origRes.y;

    this.pxlEnv.updateCompUniforms();
    
    this.pxlEnv.roomNameList.forEach( (r)=>{
      this.pxlEnv.roomSceneList[ r ].pxlCamAspect = aspectRatio ;
      //if( r != this.pxlEnv.mainRoom){
      if( this.pxlEnv.roomSceneList[ r ]?.resize ){
        this.pxlEnv.roomSceneList[ r ].resize( this.mapW, this.mapH );
      }
    });
        
    // Emit the resize calculations 
    this.emit("resize", {
      "rawWidth" : window.innerWidth,
      "rawHeight" : window.innerHeight,
      "width" : this.mapW,
      "height" : this.mapH,
      "xPixelPerc" : this.screenRes.x,
      "yPixelPerc" : this.screenRes.y,
      "aspectRatio" : aspectRatio
    });
        
    if( !this.pxlTimer.active ){
        this.pxlEnv.mapRender( false );
    }
    
  }

  // -- -- -- -- -- -- -- -- -- -- //
  // -- Module Communication -- -- //
  // -- -- -- -- -- -- -- -- -- -- //

  subscribe( eventType, callbackFn ){
    if( !this.subscriptableEvents.includes( eventType ) ){
      console.error( "Event type not subscribable: ", eventType );
      return;
    }
    if( !this.callbackList[eventType] ){
      this.callbackList[eventType] = [];
    }
    this.callbackList[eventType].push( callbackFn );
  }

  unsubscribe( eventType, callbackFn ){
    if( this.callbackList[eventType] ){
      let index = this.callbackList[eventType].indexOf( callbackFn );
      if( index >= 0 ){
        this.callbackList[eventType].splice( index, 1 );
      }
    }
  }

  emit( eventType, data ){
    if( this.callbackList.hasOwnProperty( eventType ) ){
      this.callbackList[eventType].forEach( (callbackFn)=>{
        callbackFn( data );
      });
    }
  }

}