// -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
// -- 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,
PlaneGeometry,
DoubleSide,
Mesh
} from "three";
import { ANTI_ALIASING, VERBOSE_LEVEL, COLLIDER_TYPE } from "./core/Enums.js";
import { pxlOptions } from "./core/Options.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;
this.pxlRendering=null;
// -- -- -- --
// TODO : Move to pxlQuality, when I finally get to that module
this.prevRenderMS=0;
this.prevRuntimeMS=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;
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.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.pxlRendering=pxlNav.pxlRendering;
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 ){
if( this.pxlOptions.verbose > VERBOSE_LEVEL.INFO ){
console.log( msg );
}
}
debug( ...msg ){
if( this.pxlOptions.verbose > VERBOSE_LEVEL.DEBUG ){
console.log( msg );
}
}
warn( ...msg ){
if( this.pxlOptions.verbose > VERBOSE_LEVEL.WARN ){
console.warn( msg );
}
}
error( ...msg ){
if( this.pxlOptions.verbose > VERBOSE_LEVEL.ERROR ){
console.error( 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');
let ctxVal=false;
try{
ctxVal=!!tmpCanvas.getContext('webgl');
}catch(err){
this.activeContext=true;
this.pxlGuiDraws.pxlNavCanvas.style.display='none';
}
if( !ctxVal ){
console.warn("WebGL Context lost, reloading pxlNav");
}
}
//if( this.pxlDevice.mobile && this.exposureShiftActive ){
//this.pxlCamera.colliderShiftActive=!(this.pxlCamera.colliderAdjustPerc===1 || this.pxlCamera.colliderAdjustPerc===0);
//this.pxlRendering.updateCompUniforms(curExp);
//}
this.emit( "step", {
'time': this.pxlTimer.curMS,
'delta': this.pxlTimer.msRunner.y,
});
}
// Function required, stoping functions
async stop(){
this.setExposure( 0 );
let subList=Object.keys( this.roomSubList );
subList.forEach( (s)=>{
this.roomSubList[ s ].stop();
});
}
loadRoomByName( roomName ){
let roomListNames = Object.keys( this.roomSceneList );
if( roomListNames.includes( roomName ) ){
let roomObj = this.roomSceneList[ roomName ];
this.loadRoom( roomObj );
}else{
this.warn("Room '"+roomName+"' not found, cannot load.");
}
}
loadRoom(roomObj){
return new Promise( async (resolve, reject) => {
let roomName = roomObj.roomName;
this.log("Loading Room - ", roomName);
try {
// Updated in v1.0.0 to use RoomEnvironment class objects
// This means the rooms must be imported as modules and passed to pxlNav when built
/*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;
this.debug(this.roomSceneList[ roomName ]);
resolve(true);
} catch (err) {
console.error("Error Loading Room - ", roomName);
console.error("Error details:", err.message);
if(this.pxlOptions.verbose >= VERBOSE_LEVEL.ERROR){
console.error("Full error:", 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.debug("Building Room Environments - ");
this.debug(this.roomNameList);
this.roomNameList.forEach( (r)=>{
if( this.roomSceneList[ r ].init ){
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.pxlRendering.mapOverlayHeavyPass.enabled === true ){
return this.pxlRendering.mapOverlayHeavyPass.material ;
}else if( this.pxlRendering.mapOverlayPass.enabled === true ){
return this.pxlRendering.mapOverlayPass.material ;
}else if( this.pxlRendering.mapOverlaySlimPass.enabled === true ){
return this.pxlRendering.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.pxlRendering.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.pxlRendering.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.pxlRendering.mapOverlayHeavyPass.enabled === true ){
setShaderMtl= this.pxlRendering.mapOverlayHeavyPass.material ;
}else if( this.pxlRendering.mapOverlayPass.enabled === true ){
setShaderMtl= this.pxlRendering.mapOverlayPass.material ;
}else if( this.pxlRendering.mapOverlaySlimPass.enabled === true ){
setShaderMtl= this.pxlRendering.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.pxlRendering.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.pxlRendering.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 PlaneGeometry();
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");
}
}
// -- -- -- -- -- -- -- -- -- -- -- //
// Event Helpers
sendRoomMessage( roomName, messageType, messageValue ){
if( this.roomSceneList[ roomName ] ){
this.roomSceneList[ roomName ].onMessage( messageType, messageValue );
}
}
// -- -- -- -- -- -- -- -- -- -- -- //
buildComposers(){
this.pxlRendering.buildComposers();
this.mapOverlayHeavyPass=this.pxlRendering.mapOverlayHeavyPass;
this.mapOverlayPass=this.pxlRendering.mapOverlayPass;
this.mapOverlaySlimPass=this.pxlRendering.mapOverlaySlimPass;
this.mapBoxAAPass=this.pxlRendering.mapBoxAAPass;
this.mapCrossAAPass=this.pxlRendering.mapCrossAAPass;
this.mapWorldPosMaterial=this.pxlRendering.mapWorldPosMaterial;
this.mapGlowPass=this.pxlRendering.mapGlowPass;
this.mapComposer=this.pxlRendering.mapComposer;
this.mapComposerMotionBlur=this.pxlRendering.mapComposerMotionBlur;
this.mapComposerGlow=this.pxlRendering.mapComposerGlow;
this.chromaticAberrationPass=this.pxlRendering.chromaticAberrationPass;
this.lizardKingPass=this.pxlRendering.lizardKingPass;
this.mapComposerWarpPass=this.pxlRendering.mapComposerWarpPass;
this.blurScreenMerge=this.pxlRendering.blurScreenMerge;
}
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.pxlRendering.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 ){
//
}
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.pxlRendering.engine.setRenderTarget(prepRoom.warpZoneRenderTarget);
//this.pxlRendering.engine.clear();
prepRoom.prepPortalRender();
this.pxlRendering.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.pxlRendering.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.pxlRendering.engine.setRenderTarget(null);
}
mapRender(anim=true){
if(this.pxlTimer.active){
this.step();
}
if( this.pxlTimer.runtime > this.nextRenderMS || anim===false ){
this.prevRenderMS = this.nextRenderMS;
this.nextRenderMS = this.pxlTimer.runtime + this.renderInterval;
// Render appropriate room
this.pxlRendering.stepShaderValues();
this.stepAnimatedObjects();
// Send out event to allow for any pre-render calculations
this.emit( "render-prep", {
'time': this.pxlTimer.runtime
});
let curRoom=this.roomSceneList[this.currentRoom];
if(curRoom && curRoom.booted){
curRoom.step();
curRoom.camera.layers.disable( this.pxlEnums.RENDER_LAYER.SKY );
this.pxlRendering.engine.setRenderTarget(curRoom.scene.renderTarget);
this.pxlRendering.engine.clear();
this.pxlRendering.engine.render( curRoom.scene, curRoom.camera);
this.pxlRendering.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.pxlRendering.mapWorldPosMaterial;
this.pxlRendering.engine.setRenderTarget(this.scene.renderWorldPos);
this.pxlRendering.engine.clear();
this.pxlRendering.engine.render( curRoom.scene, this.pxlCamera.camera);
curRoom.scene.overrideMaterial=null;
this.pxlCamera.camera.layers.enable( 1 );
this.pxlRendering.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.pxlRendering.roomComposer.render();
//this.pxlRendering.engine.render( this.roomSceneList[this.currentRoom].scene, this.pxlCamera.camera);
}
if( this.pxlUser.iZoom ){
this.delayComposer.render();
}
}else if( this.pxlOptions.subFrameCalculations ){
// Step room calculations for render-independent user input and collision calculations
let curRoom=this.roomSceneList[this.currentRoom];
if(curRoom && curRoom.booted){
curRoom.step();
}
}
// Send out event to allow for any post-render calculations
this.emit( "render-post", {
'time': this.pxlTimer.runtime
});
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.pxlRendering.engine.setRenderTarget(target);
let bgCd=0x000000;
let curgb = scene.background.clone()
scene.background.set( bgCd );
this.pxlRendering.engine.setClearColor(bgCd, 0);
//this.pxlRendering.engine.clear();
this.pxlRendering.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.pxlRendering.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;
}
}
}