./ pxlNav Camera manager
./ Written by Kevin Edzenga 2020; 2024
./ Note : Since item implementation was done rather hastily,
./ Triggering full screen overlays occures in the Camera class currently.
./ This include LizardKing, StarField and InfiniteZoom
./ The Low-Gravity Jump is triggered through the `User` class
./
./ TODO : Move item functions from Camera and User to an `Item` class
import {
Vector2,
Vector3,
Quaternion,
Object3D,
Euler
} from "...../libs/three/three.module.min.js";
import { pxlUserSettings } from "...core/Options.js";
import { VERBOSE_LEVEL, COLLIDER_TYPE, CAMERA_EVENT } from "...core/Enums.js";
./ TODO : Extend this damn monolith of a chunky boy
./ Camera, Player Controller, Force Influence / Collision
.**
* Camera or pxlCamera class
* @alias pxlCamera
* @class
* @description Camera or pxlCamera class
*.
export class Camera{
constructor(){
./ -- -- -- -- -- -- -- -- -- -- --
./ -- Default Camera Settings -- -- --
./ -- -- -- -- -- -- -- -- -- -- -- -- --
./ User Height
this.standingHeight=1.75; ./ ~5'9" in meters
./ Movement Speed Scalar
this.movementScalar=1.0;
./ Max Movement Settings
this.hasMovementLimit=true;
this.movementMax = 100.0; ./ Meters per second
./ Jump Height Scalar
this.jumpScalar=1.0;
./ Scale of the User & Settings
./ Standing Height, Jumping Height, Walking Speed, etc.
this.userScale=1.0;
./ Max distance (Meters) up or down, like walking up and down stairs.
this.maxStepHeight=5;
./ Once click/tap is done, how much does the last velocity ease out
./ Velocity * this.cameraEasing
this.cameraEasing = [ .55, .45 ]; ./ [ PC, Mobile ]
./ Touch screen sensitivity settings
this.touchMaxSensitivity = 800;
./ The jumping impulse per frame
./this.cameraJumpImpulse= [ 0.045, 0.085 ];// [.035,.075]; // [ Grav, Low Grav ]
this.cameraJumpImpulse= [ .75, 1.5 ];./ [.035,.075]; // [ Grav, Low Grav ]
this.cameraMaxJumpHold=[ 2.85, 2.0 ]; ./ Second; [ Grav, Low Grav ]
this.gravityCount=0;
this.gravityRate=0;
this.gravityMax=15.5; ./ Gravity at scale of 1 / avg human(1.8 meters) * 9.8mms = 5.44; But this is digital here, lower is lighter
this.gravityUPS=[.3,.15]; ./ Units Per Step(); Influence of M/S^2 .. I guess haha; [ Grav, Low Grav ]
./ -- -- -- //
./ -- -- -- //
./ -- -- -- //
this.pxlAudio=null;
this.pxlTimer=null;
this.pxlAutoCam=null;
this.pxlEnv=null;
this.pxlColliders=null;
this.pxlUser=null;
this.pxlUtils=null;
this.pxlDevice=null;
this.pxlGuiDraws=null;
this.pxlQuality=null;
this.pxlOptions=null;
this.socket=null;
this.camera=null;
this.canMove=true;
this.HDRView=false;
./ Run updateCamera
this.camUpdated=true;
this.cameraBooted=false;
./ Calculation triggers
this.hasMoved = false; ./ Player has moved camera, WASD or Arrow Keys
this.hasRotated = false; ./ Player clicked and dragged mouse/touch to rotate camera
this.hasJumped = false; ./ Initial Jump Trigger
this.hasJumpLock = false; ./ Held jump upon landing, jump again after delay, this is the delay notification
this.hasGravity = false; ./ Player isn't on the ground; due to jump or running off a ledge
./ -- -- --
./ User-Input Movement & Look Scalars
./ Only set when there is analog / float input; like gamepad thumbstick or touch 'joysticks'
this.userInputMoveScalar = {x:1,y:1};
this.userInputLookScalar = 1;
this.roomStandingHeight = { 'default' : this.standingHeight };
this.standingHeightGravInfluence=0;
this.standingMaxGravityOffset=.5; ./ Usage - ( standingHeight / standingHeightGravInfluence ) * standingMaxGravityOffset
this.walkBounceSeed=230;
this.walkBounceHeight = .3; ./ sin(walkBounce) * walkBounceHeight
this.walkBounce=0;
this.walkBouncePerc=0;
this.walkBounceRate=.025; ./ Bounce rate per frame; walkBounce + walkBounceRate
this.walkBounceEaseIn=.03; ./ Ease in bouncePerc rate up to 1; perc + easeIn
this.walkBounceEaseOut=.95; ./ Ease out bouncePerc scalar; perc * easeOut
this.posRotEasingThreshold=.01; ./ Velocity based calculations with any per frame scalar cut off value; val<posRotEasingThreshold ? 0
this.cameraMovement=[0,0]; ./ Left/Right, Forward/Back, Jump
this.cameraMovementEase=.85; ./ After key up, pre-frame rate to 0
this.cameraMoveLength=0;
this.cameraMoveLengthMult=.1; ./ cameraMoveLength scalar // ## check adding multiplier to keydown movement calculations
this.camPosBlend=.65; ./ Blend to previous position, easing movement
this.camRotXYZ=new Vector3(0,0,0);./new Vector3(0,0,0);
this.camRotPitch=new Vector2(0,0);
this.cameraJumpActive=false;
this.cameraAllowJump=true;
this.cameraJumpHeight=0;
this.cameraJumpVelocity=0;
this.cameraJumpVelocityEaseOut=.90; ./ Ease out Jump Button Influence after Button Released
this.cameraJumpInAir=false;
this.floorColliderInitialHit=false;
this.colliderValidityChecked=true; ./ Value shouldn't matter, but should Room Environments not set colliderValidity, assume checked initially
this.nearestFloorHit=new Vector3(0,0,0);
this.nearestFloorObjName=null;
this.nearestFloorHitPrev=new Vector3(0,0,0);
this.nearestFloorObjNamePrev=null;
this.gravitySourceActive=false;
this.gravityDirection=new Vector3( 0, -1, 0 );
this.gravityEaseOutRate=.50;
this.jump=0;
./ TODO : Unsure if I'd rather a contant timer for all "allowed" jumps or not
./ For now, this lock holds that the player should jump again when the timer is up
this.releaseJumpLockTime = 0;
this.releaseJumpLockDelay = .08; ./ Seconds dely between repeated jumping, its less jaring with a slight delay
this.runMain=true;
this.workerActive=false;
this.worker=null;
this.workerTransfers=false;
this.workerMessage=()=>{}; ./ Browser Compatibility
this.workerFunc=()=>{}; ./ Browser Compatibility
this.deviceKey=()=>{}; ./ Browser Compatibility
this.portalList={};
this.roomWarpZone=[];
this.colliderCurObjHit=null;
this.colliderPrevObjHit=null;
this.colliderValid=false;
this.colliderFail=false;
this.warpActive=false;
this.warpType=0;
this.warpObj=null;
this.warpTarget=null;
this.hotKeyTriggered=false;
this.eventCheckStatus=false;
this.proximityScaleTrigger=false;
this.colliderShiftActive=true;
this.colliderAdjustPerc=0;
this.colliderAdjustRate=.020;
./ ## Move to Device.js
this.gyroGravity=[0,0,0];
this.cameraPose={
alpha:null,
beta:null,
gamma:null,
alphaOffset:0,
betaOffset:0,
gammaOffset:0,
orientation:window.orientation||0,
pos:[0,0,0],
posOffset:[0,0,0],
rx:()=>{return this.beta},
ry:()=>{return this.alpha},
rz:()=>{return this.gamma},
accelZeroed:[0,0,0],
accelCalibration:10,
accelCalDiv:1.10,
accelCalCount:0,
accelTotal:[0,0,0],
accelPrev:null,
accelDelta:[0,0,0],
accelClearDelta:()=>{this.accelDelta=[0,0,0];},
};
this.uniformScalars={
curExp:1.0,
darkBase:.1,
brightBase:0.5,
exposureUniformBase:1.0,
}
./ TODO : This needs to be moved and integrated into fileIO and RoomClass
this.cameraPosLookAtNames = {
"default":{
pos:"position",
lookAt:"lookat",
},
"mobile":{
pos:"positionmobile",
lookAt:"lookatmobile",
},
"vr":{
pos:"positionvr",
lookAt:"lookatvr",
}
};
this.cameraPos=new Vector3(0,0,0);
this.cameraPrevPos=new Vector3(0,0,0);;
this.cameraPrevLookAt=new Vector3(0,0,0);;
this.cameraAim=new Vector3(0,0,1);
this.cameraAimTarget=new Vector3(0,0,0);;
this.cameraCross=new Vector3(1,0,0); ./ For Audio API faux 3d audio; UNUSED CURRENTLY
this.lookAtTargetActive=new Vector3(0,0,0);;
this.lookAtPerc=new Vector2(1,0);
this.lookAtLockPerc=0;
this.lookAtLockFader=0;
this.lookAtLockFadeRate=.01;
this.prevQuaternion=new Quaternion(); ./ Used in motionBlur shader calculations only
./this.prevWorldMatrix= new Matrix4(); // Only used if running higher quality motionBlur calculations, not needed
this.pi=3.141592653589793238462643383279;
this.touchSensitivityLimits = this.touchMaxSensitivity * this.pi;
./ Event callbacks
this.callbacks={};
this.init();
}
.**
* Sets dependencies for the Camera class.
* @param {Object} pxlNav - The navigation object containing dependencies.
* @private
*.
setDependencies( pxlNav ){
this.pxlAudio=pxlNav.pxlAudio;
this.pxlTimer=pxlNav.pxlTimer;
this.pxlAutoCam=pxlNav.pxlAutoCam;
this.pxlEnv=pxlNav.pxlEnv;
this.pxlColliders=pxlNav.pxlColliders;
this.pxlUser=pxlNav.pxlUser;
this.pxlUtils=pxlNav.pxlUtils;
this.pxlDevice=pxlNav.pxlDevice;
this.pxlGuiDraws=pxlNav.pxlGuiDraws;
this.pxlQuality=pxlNav.pxlQuality;
this.pxlOptions=pxlNav.pxlOptions;
this.socket=pxlNav.socket;
}
./ -- -- --
log( msg ){
if( this.verbose >= VERBOSE_LEVEL.INFO ){
console.log( msg );
}
}
warn( msg ){
if( this.verbose >= VERBOSE_LEVEL.WARN ){
console.warn( msg );
}
}
error( msg ){
if( this.verbose >= VERBOSE_LEVEL.ERROR ){
console.error( msg );
}
}
./ -- -- --
.**
* Initializes the Camera class and Camera Worker.
*.
./ TODO : Get worker implemented for whole of camera scripts
init(){
./ Camera Service Worker - Ray Intersect Collision Detector
./ TODO : Finish adding workers to monitor the Collider class and raycasting
var worker;
if( false && Worker ){
worker = new Worker("js.pxlBase/webWorkers/CameraWorker.js");
this.worker=worker;
./this.workerList.push( worker );
let tmpThis=this;
worker.onmessage= (event) => {
tmpThis.workerMessage(event.data);
};
worker.onerror= (event)=>{
tmpThis.workerMessage({type:"err", data:event.data});
};
./ Transferables Status
let ab= new ArrayBuffer(1);
worker.postMessage(ab, [ab]); ./ ab.byteLength -> If transfer successful
this.workerTransfers=ab.byteLength==0; ./ Can transfer ArrayBuffers directly
./ Message Functions
this.workerMessage= async ( msg )=>{
if( msg.type == "update" ){
tmpThis.updateMainValues( msg );
}
}
this.workerFunc= async ( type, state=null )=>{
tmpThis.worker.postMessage({type, state})
}
this.deviceKey= async ( key=false, state=false )=>{
if( typeof key == "number"){
}else if( typeof key == "string"){
let type="key";
tmpThis.worker.postMessage({type, key, state})
}
}
this.workerActive=true;
this.runMain=false;
this.workerFunc("init");
}
}
.**
* Updates main values from worker data.
* UNUSED CURRENTLY
* @param {Object} data - The data from the worker.
* @private
*.
updateMainValues( data ){
let {gravityRate, standingHeightGravInfluence, cameraJumpImpulse}=data;
if( gravityRate != null ){
this.gravityRate = gravityRate;
}
if( standingHeightGravInfluence != null ){
this.standingHeightGravInfluence = standingHeightGravInfluence;
}
if( cameraJumpImpulse != null ){
this.cameraJumpVelocity+=cameraJumpImpulse;
}
this.camUpdated=true;
}
.///////////////////////////////////////////
./ Update settings externally of Camera //
./////////////////////////////////////////
./ Allow access to alter the Camera's internal settings
./ Run these updates during the Room's `init()`, `start()`, or `build()` functions
./ TODO : Allow per-Room settings to be set in the Room's FBX file or Room Class
./ TODO : Move these to the User class when updated for pxlNav
./ User is currently as it was for Antibody.club, so it needs to be updated for the pxlNav module
./ Default is 1.75
.**
* Sets the user's standing height.
* @method
* @memberof pxlCamera
* @param {number} val - The user's standing height.
* @param {string} [roomName='default'] - The name of the room.
* @example
* ./ Set the user's standing height to 1.75 meters.
* this.pxlCamera.setUserHeight( 1.75 );
*.
setUserHeight( val, roomName="default" ){
val = Math.max( val, 0.01 );
if( !this.roomStandingHeight.hasOwnProperty(roomName) ){
this.roomStandingHeight[roomName]=this.standingHeight;
}
if( roomName=="default" ){
this.standingHeight=val;
}
this.roomStandingHeight[roomName]=val;
}
./ Default is 5
.**
* Sets the user's maximum step height.
* @method
* @memberof pxlCamera
* @param {number} val - The user's maximum step height.
* @example
* ./ Set the user's maximum step height to 5 meters.
* this.pxlCamera.setMaxStepHeight( 5 );
*.
setMaxStepHeight( val ){
val = Math.max( val, 0.01 );
this.maxStepHeight=val;
}
./ Default is 1
.**
* Sets the user's scale.
* @method
* @memberof pxlCamera
* @param {number} val - The user's scale.
* @example
* ./ Set the user's scale to 1.
* this.pxlCamera.setUserScale( 1 );
*.
setUserScale( val ){
val = Math.max( val, 0.01 );
this.userScale=val;
}
./ Default is 1
.**
* Sets the user's movement scalar.
* @method
* @memberof pxlCamera
* @param {number} val - The user's movement scalar.
* @example
* ./ Set the user's movement scalar to 1.
* this.pxlCamera.setMovementScalar( 1 );
*.
setMovementScalar( val ){
val = Math.max( val, 0.01 );
this.movementScalar=val;
}
./ Default is 10
.**
* Sets the user's maximum movement speed.
* @method
* @memberof pxlCamera
* @param {number} val - The user's maximum movement speed.
* @example
* ./ Set the user's maximum movement speed to 10 meters per second.
* this.pxlCamera.setMovementMax( 10 );
*.
setMovementMax( val ){
val = Math.max( val, 0.01 );
this.movementMax=val;
}
./ Default is 0.85
.**
* Sets the user's movement easing rate.
* @method
* @memberof pxlCamera
* @param {number} val - The user's movement easing rate.
* @example
* ./ Set the user's movement easing rate to 0.85.
* this.pxlCamera.setMovementEase( 0.85 );
*.
setMovementEase( val ){
val = Math.min( 1, Math.max( val, 0.01 ) );
this.cameraMovementEase=val;
}
./ Default is 0.75
.**
* Sets the jump scalar.
* @method
* @memberof pxlCamera
* @param {number} val - The jump scalar.
* @example
* ./ Set the jump scalar to 0.75.
* this.pxlCamera.setJumpScalar( 0.75 );
*.
setPositionBlend( val ){
val = Math.min( 1, Math.max( val, 0.01 ) );
this.camPosBlend=val;
}
./ Default is 0.1
.**
* Sets the user's movement multiplier.
* @method
* @memberof pxlCamera
* @param {number} val - The user's movement multiplier.
* @example
* ./ Set the user's movement multiplier to 0.1.
* this.pxlCamera.setInputMovementMult( 0.1 );
*.
setInputMovementMult( val ){
val = Math.max( val, 0.01 );
this.cameraMoveLengthMult=val;
}
./ -- -- --
./ Default is 1
.**
* Sets the user's jump scalar.
* @method
* @memberof pxlCamera
* @param {number} val - The user's jump scalar.
* @example
* ./ Set the user's jump scalar to 1.
* this.pxlCamera.setJumpScalar( 1 );
*.
setJumpScalar( val ){
val = Math.max( val, 0.01 );
this.jumpScalar=val;
}
./ Default is 0.75
.**
* Sets the user's jump impulse.
* @method
* @memberof pxlCamera
* @param {number} val - The user's jump impulse.
* @example
* ./ Set the user's jump impulse to 0.75.
* this.pxlCamera.setJumpImpulse( 0.75 );
*.
setJumpImpulse( val ){
val = Math.max( val, 0.01 );
this.cameraJumpImpulse[0]=val;
}
./ Default is 2.85
.**
* Sets the user's maximum jump hold.
* @method
* @memberof pxlCamera
* @param {number} val - The user's maximum jump hold.
* @example
* ./ Set the user's maximum jump hold to 2.85.
* this.pxlCamera.setJumpHoldMax( 2.85 );
*.
setJumpHoldMax( val ){
val = Math.max( val, 0.01 );
this.cameraMaxJumpHold[0]=val;
}
./ Default is 0.08
.**
* Sets the user's jump repeat delay.
* @method
* @memberof pxlCamera
* @param {number} val - The user's jump repeat delay.
* @example
* ./ Set the user's jump repeat delay to 0.08.
* this.pxlCamera.setJumpRepeatDelay( 0.08 );
*.
setJumpRepeatDelay( val ){
val = Math.max( val, 0.01 );
this.releaseJumpLockDelay=val;
}
./ -- -- --
./ Default is 0.85
.**
* Sets the user's camera movement easing.
* @method
* @memberof pxlCamera
* @param {number} val - The user's camera movement easing.
* @example
* ./ Set the user's camera movement easing to 0.85.
* this.pxlCamera.setCameraMoveEasing( 0.85 );
*.
setCameraRotateEasing( val ){
if( !Array.isArray(val) ){
if( typeof val == "number" ){
val=[val,val];
this.warn("Warning : Camera.setCameraEasing() expects an array of two numeric values, [PC Easing 0-1, Mobile Easing 0-1]; Default is [.55,.45]");
}else{
this.error("Error : Camera.setCameraEasing() unexpected values type; expecting an array of two numeric values, [PC Easing 0-1, Mobile Easing 0-1]; Default is [.55,.45]");
}
}
this.cameraEasing=val;
}
./ Touch Sensitivity should be a pixel-to-device reasonable value
./ Default is 500, being 500 pixels dragging range to look around
.**
* Sets the user's touch sensitivity.
* @method
* @memberof pxlCamera
* @param {number} val - The user's touch sensitivity.
* @example
* ./ Set the user's touch sensitivity to 500.
* this.pxlCamera.setTouchSensitivity( 500 );
*.
setTouchSensitivity( val ){
if(val<=0){
val=1;
}
this.touchMaxSensitivity=val;
this.touchSensitivityLimits = this.touchMaxSensitivity * this.pi;
}
.**
* Sets the user's gravity rate.
* @method
* @memberof pxlCamera
* @param {number} val - The user's gravity rate.
* @example
* ./ Set the user's gravity rate to 0.3
* this.pxlCamera.setGravityRate( 0.3 );
*.
setGravityRate( val ){
if(val<=0){
val=1;
}
this.gravityRate=val;
}
./ Assume 1 unit is 1 meter/second^2
./ But default is 2.5, so it's a bit lighter than Earth's gravity
.**
* Sets the user's gravity rate.
* @method
* @memberof pxlCamera
* @param {number} val - The user's gravity rate.
* @example
* ./ Set the user's gravity rate to 2.5
* this.pxlCamera.setGravityRate( 2.5 );
*.
setGravityMax( val ){
if(val<=0){
val=1;
}
this.gravityMax=val;
}
./ Set walking bounce settings
./ Default is 230
.**
* Sets the user's walk bounce seed.
* @method
* @memberof pxlCamera
* @param {number} val - The user's walk bounce seed.
* @example
* ./ Set the user's walk bounce seed to 230.
* this.pxlCamera.setWalkBounceSeed( 230 );
*.
setWalkBounceHeight( val ){
if(val<=0){
val=0;
}
this.walkBounceHeight=val;
}
./ Default is 0.025
.**
* Sets the user's walk bounce rate.
* @method
* @memberof pxlCamera
* @param {number} val - The user's walk bounce rate.
* @example
* ./ Set the user's walk bounce rate to 0.025.
* this.pxlCamera.setWalkBounceRate( 0.025 );
*.
setWalkBounceRate( val ){
if(val<=0){
val=0.0001;
}
this.walkBounceRate=val;
}
./ Default is 0.03
.**
* Sets the user's walk bounce ease in.
* @method
* @memberof pxlCamera
* @param {number} val - The user's walk bounce ease in.
* @example
* ./ Set the user's walk bounce ease in to 0.03
* this.pxlCamera.setWalkBounceEaseIn( 0.03 );
*.
setWalkBounceEaseIn( val ){
val = Math.min( 1, Math.max( val, 0.0001 ) );
this.walkBounceEaseIn=val;
}
./ Default is 0.95
.**
* Sets the user's walk bounce ease out.
* @method
* @memberof pxlCamera
* @param {number} val - The user's walk bounce ease out.
* @example
* ./ Set the user's walk bounce ease out to 0.95
* this.pxlCamera.setWalkBounceEaseOut( 0.95 );
*.
setWalkBounceEaseOut( val ){
val = Math.min( 1, Math.max( val, 0.01 ) );
this.walkBounceEaseOut=val;
}
./ -- -- --
./ Set camera values from `pxlUserSettings` structure
./ Should custom entries have been added, they wont be processed
./ TODO : This is a bit messy at the moment
./ TODO : Add a per-room userSettings with lookup into the `pxlUserSettings` structure
./ Since with this set structure, it doesn't need to be individual variables
.**
* Sets the user's settings.
* @method
* @memberof pxlCamera
* @param {Object} userSettingsObject - The user's settings object.
* @example
* ./ Set the user's settings.
* import { pxlUserSettings } from "...core/Options.js";
* let userSettingsObject = Object.assign({}, pxlUserSettings);
* this.pxlCamera.setUserSettings( userSettingsObject );
*.
setUserSettings( userSettingsObject ){
./ User & collider step height settings
if( userSettingsObject.hasOwnProperty("height") ){
if( userSettingsObject.height.hasOwnProperty("standing") ){
this.setUserHeight( userSettingsObject.height.standing );
}
if( userSettingsObject.height.hasOwnProperty("stepSize") ){
this.setMaxStepHeight( userSettingsObject.height.stepSize );
}
}
./ -- -- --
./ Camera movement settings
if( userSettingsObject.hasOwnProperty("movement") ){
if( userSettingsObject.movement.hasOwnProperty("scalar") ){
this.setMovementScalar( userSettingsObject.movement.scalar );
}
if( userSettingsObject.movement.hasOwnProperty("max") ){
this.setMovementMax( userSettingsObject.movement.max );
}
if( userSettingsObject.movement.hasOwnProperty("easing") ){
this.setMovementEase( userSettingsObject.movement.easing );
}
}
./ -- -- --
./ Head bounce settings
if( userSettingsObject.hasOwnProperty("headBounce") ){
if( userSettingsObject.headBounce.hasOwnProperty("height") ){
this.setWalkBounceHeight( userSettingsObject.headBounce.height );
}
if( userSettingsObject.headBounce.hasOwnProperty("rate") ){
this.setWalkBounceRate( userSettingsObject.headBounce.rate );
}
if( userSettingsObject.headBounce.hasOwnProperty("easeIn") ){
this.setWalkBounceEaseIn( userSettingsObject.headBounce.easeIn );
}
if( userSettingsObject.headBounce.hasOwnProperty("easeOut") ){
this.setWalkBounceEaseOut( userSettingsObject.headBounce.easeOut );
}
}
./ -- -- --
./ Jump settings
if( userSettingsObject.hasOwnProperty("jump") ){
if( userSettingsObject.jump.hasOwnProperty("impulse") ){
this.setJumpScalar( userSettingsObject.jump.impulse );
}
if( userSettingsObject.jump.hasOwnProperty("holdMax") ){
this.setJumpHoldMax( userSettingsObject.jump.holdMax );
}
if( userSettingsObject.jump.hasOwnProperty("repeatDelay") ){
this.setJumpRepeatDelay( userSettingsObject.jump.repeatDelay );
}
}
./ -- -- --
./ Gravity settings
./ From a Jump or running off a ledge
if( userSettingsObject.hasOwnProperty("gravity") ){
if( userSettingsObject.gravity.hasOwnProperty("ups") ){
this.setGravityRate( userSettingsObject.gravity.ups );
}
if( userSettingsObject.gravity.hasOwnProperty("max") ){
this.setGravityMax( userSettingsObject.gravity.max );
}
}
}
.////////////////////////
./ Main runtime loop //
.//////////////////////
.**
* Main step function to update camera state.
* @private
*.
step(){
./ Update camera position with out gravity, jump, or collider influences.
./ Simply apply initial frame movement vectors
if(this.pxlDevice.directionKeyDown){
this.updateMovement(this.pxlTimer.prevMS);
}
if( this.runMain ){
if( this.hasJumpLock && this.pxlTimer.runtime > this.releaseJumpLockTime ){
this.hasJumpLock = false;
this.hasGravity = false;
this.cameraAllowJump = true;
this.camInitJump();
}
if( this.hasGravity && this.cameraJumpActive ){
this.camJump(this.pxlTimer.prevMS);
}else if(this.cameraJumpVelocity>0 ){
this.killJumpImpulse();
}
}
./ Check if camera calculations should be ran
this.camUpdated= this.camUpdated || this.hasMoved || this.hasRotated || this.hasGravity || this.cameraMovement[0]!=0 || this.cameraMovement[1]!=0 ;./ || this.pxlDevice.cursorLockActive;
this.updateCamera();
./ Update Shaders to Camera Position / Rotation changes
this.lowQualityUpdates();
this.midQualityUpdates();
this.eventCheck();
}
.**
* Checks for events based on environment triggers.
* Currently only checking for Ground Collider, Room Warps, and Portals
* @private
*.
eventCheck(){
if( this.colliderValid && this.eventCheckStatus){
./ Camera Translate Events don't need further calculatons; Room Warps and Portals
if( this.eventTrigger(this.nearestFloorObjName) ){
this.warpEventTriggered(1, this.nearestFloorObjName);
./return;
}
}
}
.**
* Updates device values based on velocity easing magnitude.
* @param {number} velEaseMag - The current velocity magnitude.
* @private
*.
updateDeviceValues( velEaseMag ){
if(!this.pxlQuality.settings.leftRight){
let tankRotate=-this.cameraMovement[0];
if(!this.pxlDevice.touchMouseData.active){
this.pxlDevice.touchMouseData.velocity.x+=tankRotate;
./this.pxlDevice.touchMouseData.velocityEase.x+=tankRotate;
}
this.pxlDevice.touchMouseData.netDistance.x+=tankRotate*4.0;
}
./let stillMoving=false;
./ PC Mouse Movement
if(this.pxlDevice.touchMouseData.velocity!=null && this.pxlDevice.mobile==0){
if(velEaseMag<this.posRotEasingThreshold){
this.pxlDevice.touchMouseData.velocity.multiplyScalar(0);
./this.pxlDevice.touchMouseData.velocityEase.multiplyScalar(0);
}else{
let curEasing = this.cameraEasing[ (this.pxlDevice.mobile?1:0) ];
this.pxlDevice.touchMouseData.velocity.multiplyScalar( curEasing );
./this.pxlDevice.touchMouseData.velocityEase.multiplyScalar( curEasing );
}
this.pxlDevice.touchMouseData.netDistance.add( this.pxlDevice.touchMouseData.velocity.clone().multiply( this.pxlDevice.touchMouseData.moveMult ) );
}
}
./////////////////////////////////////////
./ Gyroscope Enabled Device Functions //
.///////////////////////////////////////
.**
* Builds device-pose monitors for gyroscope-enabled devices.
* CURRENTLY UNWORKING
* NOTE : Development in-progress through 'Device.js'
* @private
*.
buildDeviceMonitors(){
let camObject=this;
./window.addEventListener('deviceorientation', (e)=>{camObject.devicePoseData(camObject,e)} );
./window.addEventListener('orientationchange', (e)=>{camObject.deviceOrientationData(camObject,e)} );
./window.addEventListener('devicemotion', (e)=>{camObject.deviceMotionData(camObject,e)} );
./%=
./gyroscope=new Gyroscope({frequency:10});
./gyroscope.addEventListener('reading',gyroPoseData);
./gyroscope.start();
./window.addEventListener('devicemotion', deviceMotionData);
./window.addEventListener('orientationchange', devicePoseChange); // Don't know when this ever fires, docs seem like it should run tho
./ Based around w3.org's Accelerometer builder
.* navigator.permissions.query({ name: 'accelerometer' }).then(result => {
if (result.state === 'denied') {
console.log('Permission to use accelerometer sensor is denied.');
return;
}
let acl = new Accelerometer({frequency: 10});
let max_magnitude = 0;
acl.addEventListener('activate', () => console.log('Ready to measure.'));
acl.addEventListener('error', error => console.log(`Error: ${error.name}`));
acl.addEventListener('reading', () => {
let magnitude = Math.hypot(acl.x, acl.y, acl.z);
if (magnitude > max_magnitude) {
max_magnitude = magnitude;
console.log(`Max magnitude: ${max_magnitude} m.s2`);
}
});
acl.start();
});*.
./%
}
.*
gyroPoseData(camObj,e){
let x=gyroscope.x;
let y=gyroscope.y;
let z=gyroscope.z;
let prevGyro=[...this.pxlDevice.gyroGravity];
this.pxlDevice.gyroGravity=[x,y,z];
accumGravity=[accumGravity[0]+x-prevGyro[0],accumGravity[1]+y-prevGyro[1],accumGravity[2]+z-prevGyro[2]];
}
deviceMotionData(camObj,e){
let acc=e.acceleration;./IncludingGravity;
./let ag=e.accelerationIncludingGravity;
if(camObj.cameraPose.accelCalCount<camObj.cameraPose.accelCalibration){
camObj.cameraPose.accelCalCount+=1;
camObj.cameraPose.accelZeroed[0]+=acc.x;
camObj.cameraPose.accelZeroed[1]+=acc.y;
camObj.cameraPose.accelZeroed[2]+=acc.z;
if(camObj.cameraPose.accelCalCount==camObj.cameraPose.accelCalibration){
camObj.cameraPose.accelZeroed[0]*=camObj.cameraPose.accelCalDiv;
camObj.cameraPose.accelZeroed[1]*=camObj.cameraPose.accelCalDiv;
camObj.cameraPose.accelZeroed[2]*=camObj.cameraPose.accelCalDiv;
}
}
camObj.cameraPose.accelDelta[0]=acc.x-camObj.cameraPose.accelZeroed[0];
camObj.cameraPose.accelDelta[1]=acc.y-camObj.cameraPose.accelZeroed[1];
camObj.cameraPose.accelDelta[2]=acc.z-camObj.cameraPose.accelZeroed[2];
camObj.cameraPose.accelTotal[0]+=acc.x-camObj.cameraPose.accelZeroed[0];
camObj.cameraPose.accelTotal[1]+=acc.y-camObj.cameraPose.accelZeroed[1];
camObj.cameraPose.accelTotal[2]+=acc.z-camObj.cameraPose.accelZeroed[2];
}
devicePoseData(camObj,e){
if(e.alpha!==null){
camObj.abs=e.absolute;
camObj.cameraPose.gamma=e.gamma; ./ Yaw
camObj.cameraPose.beta=e.beta; ./ Pitch
camObj.cameraPose.alpha=e.alpha; ./ Roll
camObj.cameraPose.alphaOffset=e.alphaOffset||0; ./ Roll
}
}
deviceOrientationData(camObj,e){
camObj.cameraPose.orientation=window.orientation||0;
}
*.
.////////////////////////////////////////////////////////////////////
./ Set Camera Position / Look At Functions; Room Warps & Portals //
.//////////////////////////////////////////////////////////////////
.**
* Updates camera matrices.
* Updates - Projection Matrix, Matrix World, & World Matrix
* @example
* import { Object3D } from "three";
* let emptyObject=new Object3D();
*
* emptyObject.position.set(10,10,0);
*
* this.pxlCamera.setTransform(emptyObject.position);
*
* ./ Update camera matrices.
* this.pxlCamera.updateCameraMatrices();
*.
updateCameraMatrices(){
this.camera.updateProjectionMatrix();
this.camera.updateMatrixWorld();
this.camera.updateWorldMatrix();
}
.**
* Resets camera calculations to a Vector3.
* @param {Vector3} newPosition - The new position for the camera.
* @example
* import { Vector3 } from "three";
* let newPos=new Vector3(20,5,15);
* this.pxlCamera.resetCameraCalculations(newPos);
*.
resetCameraCalculations( newPosition ){
this.cameraMovement[0] = 0;
this.cameraMovement[1] = 0;
this.pxlDevice.touchMouseData.curFadeOut.multiplyScalar(0);
this.pxlDevice.touchMouseData.velocity.multiplyScalar(0);
this.pxlDevice.touchMouseData.netDistance.set(0,0);
this.camera.position.copy( newPosition );
this.updateCameraMatrices();
this.cameraPos.copy( newPosition );
this.cameraPrevPos.copy( newPosition );
./ Force collision detection
this.colliderCurObjHit=null;
this.colliderPrevObjHit=null;
this.camUpdated=true; ./ Forces next frame update
this.hasMoved=true; ./ Forces next frame update
}
.**
* Sets the field-of-view for the camera.
* @param {number} fov - The field-of-view.
* @example
* ./ Set the field-of-view to 75.
* this.pxlCamera.setFOV( 75 );
*.
setFOV( fov ){
this.camera.fov=fov;
this.camera.updateProjectionMatrix();
this.camUpdated=true;
}
.**
* Sets camera stats including FOV, aspect ratio, near and far clipping planes.
* Used when changing Rooms
* @param {number} fov - The field of view.
* @param {number} aspect - The aspect ratio.
* @param {number} near - The near clipping plane.
* @param {number} far - The far clipping plane.
* @example
* ./ Set the camera stats.
* ./ FOV: 75, Aspect: 1.33, Near: 0.1, Far: 1000
* this.pxlCamera.setStats( 75, 1.33, 0.1, 1000 );
*.
setStats( fov, aspect, near, far ){
./ TODO : Aspect is weird, I need to work out better calculations for this
./this.camera.aspect=aspect;
this.camera.near=near;
this.camera.far=far;
this.setFOV(fov);
}
setAspect( aspect ){
this.camera.aspect=aspect;
this.camera.updateProjectionMatrix();
./this.camUpdated=true;
}
.**
* Sets the camera transform to a specific position and lookAt target.
* For camera position changes, portals, and room warps
* @param {Vector3} pos - The position to set the camera.
* @param {Vector3} [lookAt=null] - The lookAt target.
* @example
* import { Vector3 } from "three";
* let pos=new Vector3(10,15,15);
* let lookAt=new Vector3(0,5,0);
*
* this.pxlCamera.setTransform(pos, lookAt);
*.
setTransform(pos, lookAt=null){ ./ Vector3, Vector3
this.resetCameraCalculations(pos); ./ Reinitiates Camera; Forces collision detection, Kills user inputs
if(lookAt){
this.cameraAimTarget.position.copy( lookAt );
this.camera.lookAt(lookAt);
this.cameraPrevLookAt.copy( lookAt );
this.updateCameraMatrices();
this.pxlDevice.touchMouseData.initialQuat=this.camera.quaternion.clone();
}
this.resetGravity();
this.camUpdated=true; ./ Forces next frame update
}
.**
* Best used without a `lookAt` target to allow the object's rotation to be adopted.
* If you just want to move the camera without rotational changes -or- to an object with a lookAt, it's better to use `setTransform`
* @param {Object3D} obj - The object to set the camera to.
* @param {Object3D|string} [lookAt=null] - The lookAt Camera Position Name or target object.
* If a string is provided, it checks for the camera position if it exists in the Room,
* Ussuall set in your FBX file.
* @example
* import { Object3D } from "three";
* let obj=new Object3D();
* obj.position.set(10,15,15);
*
* this.pxlCamera.setToObj(obj);
*.
setToObj(obj, lookAt=null){ ./ Object3D, Object3D
this.resetCameraCalculations( obj.position ); ./ Reinitiates Camera; Forces collision detection, Kills user inputs
./ If no lookat, adopt Object rotation
if(lookAt){
let toLookAt=lookAt.position.clone();
this.cameraAimTarget.position.copy( toLookAt );
this.camera.lookAt(toLookAt);
this.cameraPrevLookAt.copy( toLookAt );
this.updateCameraMatrices();
this.pxlDevice.touchMouseData.initialQuat=this.camera.quaternion.clone();
}else{
this.pxlDevice.touchMouseData.initialQuat=obj.quaternion.clone();
this.camera.setRotationFromQuaternion(this.pxlDevice.touchMouseData.initialQuat);
this.updateCameraMatrices();
}
this.resetGravity();
this.camUpdated=true; ./ Forces next frame update
this.hasMoved=true;
this.hasRotated=true;
this.colliderCheck( obj.position );
this.nearestFloorObjName=null;
}
.**
* Warps the camera to a specific room.
* @param {string} roomName - The name of the room to warp to.
* @param {boolean} [start=false] - Whether to run the room's `start()` function.
* @param {Object3D} [objTarget=null] - The target object in the room.
* @example
* ./ Warp the camera to the default location in "OutletEnvironment" room.
* this.pxlCamera.warpToRoom( "OutletEnvironment", true );
*
* ./ Warp to the 'aboutMe' camera position in "CampfireEnvironment" room.
* ./ Note, camera positions are set in your FBX by the camera's group name.
* this.pxlCamera.warpToRoom( "CampfireEnvironment", true, "aboutMe" );
*.
warpToRoom(roomName, start=false, objTarget=null){
let roomKeys = Object.keys(this.pxlEnv.roomSceneList);
let roomEnv = this.pxlEnv.roomSceneList[this.pxlEnv.currentRoom]
objTarget = objTarget || roomEnv.defaultCamLocation || "";
objTarget = objTarget.toLowerCase()
let camLocName = roomName.toLowerCase();
let hasCurrentRoom = this.pxlEnv.roomSceneList.hasOwnProperty( this.pxlEnv.currentRoom );
if( roomEnv && !roomKeys.includes(roomName) && roomEnv.camLocation.hasOwnProperty(camLocName) ){
objTarget = camLocName;
roomName = this.pxlEnv.currentRoom;
}else{
if( hasCurrentRoom){
this.pxlEnv.roomSceneList[this.pxlEnv.currentRoom].stop();
}
roomEnv = this.pxlEnv.roomSceneList[roomName];
};
let prevRoom=this.pxlEnv.currentRoom;
let holdCamera=false;
if( hasCurrentRoom && this.pxlEnv.roomSceneList[this.pxlEnv.currentRoom].hasOwnProperty( "camHoldWarpPos" )){
holdCamera = this.pxlEnv.roomSceneList[this.pxlEnv.currentRoom].camHoldWarpPos;
}
this.pxlEnv.currentRoom=roomName;
this.pxlAutoCam.curRoom=roomName;
let isMainRoom=roomName==this.pxlEnv.mainRoom;
./this.pxlEnv.delayPass.uniforms.roomComposer.value= isMainRoom ? 0 : 1;
if( this.pxlUser.iZoom ){
let tDiff= isMainRoom ? this.pxlEnv.roomComposer : this.pxlEnv.mapComposer;
let tDiffPrev= isMainRoom ? this.pxlEnv.mapComposer: this.pxlEnv.roomComposer;
this.pxlEnv.delayPass.uniforms.tDiffusePrev.value= tDiff.renderTarget1.texture;
this.pxlEnv.delayPass.uniforms.tDiffusePrevRoom.value= tDiffPrev.renderTarget1.texture;
setTimeout( ()=>{
if( prevRoom != roomName ){
if( isMainRoom ){
this.pxlEnv.roomComposer.reset();
}else{
this.pxlEnv.mapComposer.reset();
}
}
setTimeout( ()=>{
this.pxlEnv.mapComposerWarpPass.needsSwap=false;
},500);
},100);
}
./this.pxlEnv.delayPass.uniforms.tDiffusePrev.value= roomName==this.pxlEnv.mainRoom ? this.pxlEnv.mapComposer.renderTarget1.texture : this.pxlEnv.roomComposer.renderTarget1.texture;
./if(roomName!=this.pxlEnv.mainRoom || start){
if( start ){
if( !roomEnv ){
this.warn("pxlCamera.warpToRoom(); pxlRoom not found - "+this.pxlEnv.currentRoom);
return;
}
if( roomName != prevRoom ){
roomEnv.start();
}
this.pxlEnv.roomRenderPass.scene=roomEnv.scene;
let camLocName = objTarget.toLowerCase();
if( roomEnv.camLocation.hasOwnProperty( camLocName ) ){
let posName = this.cameraPosLookAtNames["default"].pos;
let lookAtName = this.cameraPosLookAtNames["default"].lookAt;
if( this.pxlDevice.mobile ){
if( roomEnv.camLocation[ camLocName ].hasOwnProperty( this.cameraPosLookAtNames["mobile"].pos ) ){
posName=this.cameraPosLookAtNames["mobile"].pos;
}
if( roomEnv.camLocation[ camLocName ].hasOwnProperty( this.cameraPosLookAtNames["mobile"].lookAt ) ){
lookAtName=this.cameraPosLookAtNames["mobile"].lookAt;
}
}
let toPos = roomEnv.camLocation[ camLocName ][ posName ];
let toLookAt = roomEnv.camLocation[ camLocName ][ lookAtName ];
this.setTransform( toPos, toLookAt );
}else if( roomEnv.camInitPos && roomEnv.camInitLookAt && ( !holdCamera || !this.pxlEnv.postIntro || this.hotKeyTriggered ) ){
this.setTransform( roomEnv.camInitPos, roomEnv.camInitLookAt );
this.hotKeyTriggered=false;
}
}else{
if( !holdCamera || !this.pxlEnv.postIntro || this.hotKeyTriggered ){
if(objTarget!=null){
this.setToObj(objTarget);
}else{
this.setTransform( roomEnv.camReturnPos, roomEnv.camReturnLookAt );
}
this.hotKeyTriggered=false;
}
}
this.pxlGuiDraws.prepArtistInfo( roomEnv.getArtistInfo() );
this.camUpdated=true;
.*this.pxlEnv.mapComposerWarpPass.enabled=!this.pxlEnv.mapComposerWarpPass.enabled;
this.pxlEnv.mapComposer.render();
this.pxlEnv.mapComposerWarpPass.enabled=!this.pxlEnv.mapComposerWarpPass.enabled;
this.pxlEnv.mapComposer.render();*.
let curFOV = roomEnv.pxlCamFOV[ this.pxlDevice.mobile ? 'MOBILE' : 'PC' ];
this.camera.fov=curFOV;
this.camera.zoom=roomEnv.pxlCamZoom;
this.camera.aspect=roomEnv.pxlCamAspect;
this.camera.near=roomEnv.pxlCamNearClipping;
this.camera.far=roomEnv.pxlCamFarClipping;
this.camera.updateProjectionMatrix();
let standingHeight=this.getUserHeight();
this.emitCameraTransforms( this.camera.position.clone(), standingHeight, true );
./ Camera assumes the player is warping to safe ground
./ So it will treat the position as the nearest floor hit
./ This was causing initial jumping and movement to use the camera position as the floor
./ This will drop the player from any height to the nearest floor using gravity when warping to a room
./ TODO : Move this to be Room specific
if( this.canMove ){
this.colliderValid=false;
this.hasGravity=true;
}
this.pxlAutoCam.checkStatus();
}
.**
* Get a snapshot of the room to use as the diffuse texture for the warp effect.
* @private
*
*.
warpToRoomSnapshot(roomName){
this.pxlEnv.currentRoom=roomName;
let roomEnv=this.pxlEnv.roomSceneList[this.pxlEnv.currentRoom];
let curFOV = roomEnv.pxlCamFOV[ this.pxlDevice.mobile ? 'MOBILE' : 'PC' ];
this.camera.fov=curFOV;
this.camera.zoom=roomEnv.pxlCamZoom;
this.camera.aspect=roomEnv.pxlCamAspect;
this.camera.near=roomEnv.pxlCamNearClipping;
this.camera.far=roomEnv.pxlCamFarClipping;
this.camera.updateProjectionMatrix();
this.setTransform( roomEnv.camThumbPos, roomEnv.camThumbLookAt );
let standingHeight=this.getUserHeight();
this.emitCameraTransforms( this.camera.position.clone(), standingHeight, true );
}
./ -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
./
.**
* Initiates fast travel to a specific location.
* This begins the warp effect process,
* It doesn't affect camera upon triggering, just queuing the warp event
*
* *Implementation has been disabled until further development*
*
* @param {number} [hotkey=0] - The hotkey for the fast travel location.
*
*.
fastTravel(hotkey=0){
if( this.pxlAutoCam.enabled ){
return;
}
if( this.pxlAutoCam.active || this.pxlAutoCam.autoCamActive ){
this.pxlAutoCam.preAutoCamToggle();
}
this.hotKeyTriggered=true;
if( hotkey == 0 ){ ./ Lobby
./this.warpEventTriggered( 1, this.pxlEnv.mainRoom, 'init' );
this.warpEventTriggered( 1, this.pxlEnv.currentRoom, 'init' );
}
./ Hotkeys are set for a specific scene, make 3d scene file dependant
return;
if( hotkey == 1 ){ ./ Canyon
./this.warpEventTriggered( 1, this.pxlEnv.mainRoom, this.portalList['Portal_8'] );
}else if( hotkey == 2 ){ ./ Dance Hall
./this.warpEventTriggered( 1, "ShadowPlanet", 'init' );
}else if( hotkey == 3 ){ ./ Sunflower Room
./this.warpEventTriggered( 1, this.pxlEnv.mainRoom, this.portalList['Portal_0'] );
}
}
.///////////////////////////////////
./ Camera Jumping Functions //
./////////////////////////////////
.**
* Handles the camera jump key press or release.
* @param {boolean} [jumpKeyIsDown=false] - Whether the jump (Default : Space) key is pressed.
* @example
* ./ Handle the camera jump key press or release.
* ./ Player has pressed the jump key
* this.pxlCamera.camJumpKey( true );
*
* ./ Player has released the jump key
* this.pxlCamera.camJumpKey( false );
*.
camJumpKey( jumpKeyIsDown=false ){
if( jumpKeyIsDown ){ ./ Space is down
this.camInitJump();
}else{ ./ Space is up
if(this.cameraJumpActive){ ./ Space
this.cameraJumpActive=false;
}
this.cameraAllowJump=true;
this.hasJumpLock=false; ./ Prevent repeated jumping if Space held down after landing
}
}
.**
* Initializes the jump values for the camera.
* @private
*.
camInitJump(){
./ Link static camera to prevent jumping as well
if( !this.canMove ){return;}
if( !this.hasGravity && this.cameraAllowJump ){
this.pxlDevice.keyDownCount[2]=this.pxlTimer.prevMS;
this.cameraAllowJump=false; ./ Prevent jump spamming up stacked colliders; this may be desired for ladders
this.cameraJumpActive=true;
this.cameraJumpInAir=true;
this.hasGravity=true;
this.gravityRate=0;
this.cameraJumpVelocity=this.cameraJumpImpulse[this.pxlUser.lowGrav] * this.userScale;
if( this.hasJumpLock ){
this.hasJumpLock=false;
this.nearestFloorHit=this.nearestFloorHitPrev;
}
}
}
.**
* Handles the camera jump step.
* Step the jump while impulse isn't 0.
* @param {number} curTime - The current time.
* @private
*.
camJump(curTime){
let timeDelta= (curTime-this.pxlDevice.keyDownCount[2]) ;
let fpsRateAdjust=1;./Math.min(1, 1/(20*this.pxlTimer.msRunner.y));
./ let jumpPerc=Math.min(1, timeDelta/(this.cameraMaxJumpHold[this.pxlUser.lowGrav]*fpsRateAdjust) );
let jumpPerc=Math.min(1, timeDelta . (this.cameraMaxJumpHold[this.pxlUser.lowGrav] ) ) ;
if(this.cameraJumpActive){
let jumpRate=jumpPerc ;
if(jumpRate==1){
this.cameraJumpActive=false;
}else{
jumpRate=(1-jumpRate)*(1-jumpRate);
jumpRate=jumpRate* ( jumpRate*.5+.5);
}
this.cameraJumpVelocity+=Math.max(0, jumpRate) * this.cameraJumpImpulse[this.pxlUser.lowGrav] * this.jumpScalar * this.pxlTimer.deltaTime;
}
this.cameraJumpVelocity*=(1-jumpPerc);./*.5+.5;
if( jumpPerc==1 ){
this.cameraJumpActive=false;
}
}
.**
* Kills the jump impulse.
* Space released before max jump
* @example
* ./ Kill the jump impulse.
* this.pxlCamera.killJumpImpulse();
*.
killJumpImpulse( scalar=1){
let toImpulse=this.cameraJumpVelocity * scalar * this.cameraJumpVelocityEaseOut;
this.cameraJumpVelocity= toImpulse>.1 ? toImpulse : 0;
this.workerFunc( "killJumpImpulse" );
}
.////////////////////////
./ Gravity Functions //
.//////////////////////
.**
* Updates the gravity for the camera.
* Gravity is updated and offset landing height with an ease back to standing upright
* Ran internally during the main loop, but revealed for external access
* @param {number} deltaTime - The time since the last frame.
* @example
* ./ Update the gravity for the camera.
* this.pxlCamera.updateGravity( this.pxlTimer.deltaTime );
*.
updateGravity( deltaTime ){
if( this.runMain ){
this.gravityRate = Math.max(0, this.gravityRate-this.cameraJumpVelocity );
let gravityRate = this.gravityUPS[ this.pxlUser.lowGrav ];
if( this.hasGravity ){
this.gravityCount += (gravityRate * 2.5) * deltaTime;
./this.gravityRate = Math.min( 1, ((this.gravityRate+this.gravityMax)*deltaTime)) * gravityRate;
this.gravityRate = Math.min( 1, ((this.gravityRate+this.gravityMax))) * this.gravityCount;
./this.gravityRate += this.gravityRate * this.gravityCount;
}
if( this.gravityRate != 0 ){
./ gMult not used, testing for need
let gMult=1;
if( !this.hasGravity ){
this.gravityRate=this.gravityRate>.01 ? this.gravityRate*this.gravityEaseOutRate*gravityRate * deltaTime : 0;
gMult= this.gravityRate;
}else{
./gMult=this.gravityRate*.08*gravityRate;
gMult=this.gravityRate;
}
gMult=Math.min(1, gMult);
this.standingHeightGravInfluence = Math.min(1, this.gravityRate . this.gravityMax ) * this.standingMaxGravityOffset;
./this.standingHeightGravInfluence = Math.min(1, gMult / this.gravityMax ) * this.standingMaxGravityOffset;
./this.standingHeightGravInfluence = Math.min(this.gravityMax, this.gravityRate * deltaTime ) * this.standingMaxGravityOffset;
./this.standingHeightGravInfluence = Math.min(this.gravityMax, this.gravityRate * deltaTime ) * this.standingMaxGravityOffset;
}
}
}
.**
* Resets the gravity for the camera.
* Ran during Jump Landing, Room & Portal Warps currently
* @example
* ./ Reset the gravity for the camera.
* this.pxlCamera.resetGravity();
*.
resetGravity(){
this.gravityCount=0;
this.gravityRate=0;
this.workerFunc( "resetGravity" );
this.jumpLanding( false ); ./ resetGravity runs jumpLanding on Worker
}
.**
* Handles the jump landing, stopping the camera jump.
* @param {boolean} [send=true] - Whether to send the landing event.
* @example
* ./ Force the camera to "land" from a jump
* ./ Player will still be in the air, but gravity will be active
* this.pxlCamera.jumpLanding();
*.
jumpLanding( send=true ){
./ Is space held down?
./ Trigger short delay before triggering a jump again
if( this.cameraJumpActive ){
this.hasJumpLock=true;
this.releaseJumpLockTime = this.pxlTimer.runtime + this.releaseJumpLockDelay;
}
this.gravityCount=0;
this.hasGravity=false; ./ Should probably name it cameraInAir
this.cameraJumpVelocity=0;
this.cameraJumpInAir=false; ./ hasGravity should indicate this value; residual from logic rework
this.cameraJumpActive=false; ./ Stop running camJump function
./ Leave outside if for external access
if( send ){
this.workerFunc( "jumpLanding" );
}
}
./////////////////////////////////////////////
./ Collision Objects and Ground Functions //
.///////////////////////////////////////////
.**
* Checks for main collider interactions.
* Ground plane, obstacles, and no terrain (If there are colliders in scene)
* @param {Vector3} curCamPos - The current camera position.
* @returns {Vector3} - The updated camera position.
* @example
* ./ Check for main collider interactions.
* let curPos=this.pxlCamera.colliderCheck( this.camera.position );
* ./ Update the camera position.
* this.setTransform( curPos );
*.
./ TODO : gravitySource should probably originate from Room's object list, but for now...
./ TODO : Collision check shouldn't run if no cam movement length aside from gravity, store floor name and collision position from prior run
colliderCheck( curCamPos ){
./ Check if camera is in Roam or Static mode
if( !this.canMove ){
return curCamPos;
}
./ Floor Collider
./ geoList["floorCollider"] Collision Detection
let objHit=null;
this.movementBlocked=false;
if( (this.cameraMoveLength>0 || this.colliderPrevObjHit==null || this.nearestFloorObjName==null) &&
this.cameraBooted && this.pxlEnv.roomSceneList[this.pxlEnv.currentRoom].collidersExist
){
this.colliderValidityChecked=true; ./ Prevent doublechecking object validity post collision detection
let curRoomObj = this.pxlEnv.roomSceneList[ this.pxlEnv.currentRoom ];
let castDir=new Vector3(0,-1,0);
let castPos=curCamPos.clone();./.add(new Vector3(0,100,0));
let castHeight= 150*this.maxStepHeight ;
castPos.y += castHeight + this.maxStepHeight;
let resetKeyDown=false;
var rayHits=[];
let curQuadrant= ( ~~(castPos.x>0)+"" ) + ( ~~(castPos.z>0)+"" );
if( curRoomObj.hasColliderType( COLLIDER_TYPE.WALL ) ){
rayHits = this.pxlColliders.castRay( this.pxlEnv.currentRoom, castPos, castDir, COLLIDER_TYPE.WALL );
}
if(rayHits.length > 0){ ./ Hit a wall, check if you are standing on the wall
./ this.floorColliderInitialHit=true;
curRoomObj.hitColliders( rayHits, COLLIDER_TYPE.WALL );
if(curRoomObj.hasColliderType( COLLIDER_TYPE.WALL_TOP )){
let rayTopHits=this.pxlColliders.castRay( this.pxlEnv.currentRoom, castPos, castDir, COLLIDER_TYPE.WALL_TOP );
if( rayTopHits.length > 0 ){
curRoomObj.hitColliders( rayTopHits, COLLIDER_TYPE.WALL_TOP );
}
let closestDist=-99999;
let yPrevRef=curCamPos.y;
let curName;
let curCollisionPos=this.nearestFloorHit;
let validHitCheck=false;
for(var x=0; x<rayTopHits.length;++x){
var obj=rayTopHits[x];
curName=obj.object.name;
let curHit=obj.pos;
let curDist=obj.dist;
let camDist=curHit.y;./- curCamPos.y; // ## Why??
let validHit=camDist < this.maxStepHeight;
validHitCheck = validHitCheck ? validHitCheck : validHit;
if( (curDist<closestDist && valid) || objHit==null){
objHit=curName;
closestDist=curDist;
curCollisionPos=curHit;
}
}
./ Check if camera is on top of wall
let pullBack;
if( !validHitCheck || ((curCamPos.y) < curCollisionPos.y && (this.nearestFloorHitPrev.y-curCollisionPos.y > (this.maxStepHeight+this.getStandingHeight()) && !this.hasGravity) && ( (curCamPos.y+this.maxStepHeight+this.getStandingHeight()) < curCollisionPos.y && this.hasGravity) ) ){
./objHit=this.nearestFloorObjName;
if(this.cameraMovement[0] != 0 || this.cameraMovement[1] != 0 ){
validHitCheck=true;
this.hasGravity=false;
this.hasJumpLock=true;
}
pullBack=this.cameraPos.clone();
pullBack.y=Math.min(curCamPos.y,pullBack.y);./+this.cameraJumpVelocity;
curCamPos=pullBack;
curCollisionPos=curCamPos;
if(this.hasGravity){
curCollisionPos.y=this.nearestFloorHitPrev.y;
}else{
curCollisionPos.y=this.cameraPos.y;
}
this.cameraJumpActive=false;
this.cameraAllowJump=true;
this.cameraJumpInAir=false;
this.cameraMovement[0] = 0;
this.cameraMovement[1] = 0;
this.pxlDevice.touchMouseData.curFadeOut.multiplyScalar(0);
this.pxlDevice.touchMouseData.velocity.multiplyScalar(0);
./this.pxlDevice.touchMouseData.velocityEase.multiplyScalar(0);
}
./this.colliderValid=validHitCheck;
if( validHitCheck ){
if( objHit == null ){
this.nearestFloorHit = this.nearestFloorHitPrev;
this.nearestFloorObjName = this.nearestFloorObjNamePrev;
if( Math.abs(curCamPos.y-this.nearestFloorHit.y) > (this.maxStepHeight+this.getStandingHeight()) ){
this.colliderValid = false;
this.hasGravity = true;
}
}else{
this.nearestFloorHitPrev = this.nearestFloorHit;
this.nearestFloorObjNamePrev = this.nearestFloorObjName;
this.nearestFloorHit = curCollisionPos;
this.nearestFloorObjName = objHit;
}
}
}else{
this.colliderFail=true;
this.movementBlocked=true;
}
}else{
./ ## Find orientation to gravitational source if any exist in Room Environment
let stepUpDist=this.maxStepHeight;./+this.cameraJumpVelocity;
let validDistRange=stepUpDist+this.getStandingHeight();
./castPos.y=curCamPos.y+stepUpDist;
castPos=curCamPos.clone(); ./.add(new Vector3(0,100,0));
castPos.y += castHeight + this.maxStepHeight;
if( this.pxlEnv.roomSceneList[this.pxlEnv.currentRoom].hasColliders() ){
rayHits=this.pxlColliders.castRay( this.pxlEnv.currentRoom, castPos, castDir, COLLIDER_TYPE.FLOOR );
}else{
return curCamPos; ./ No colliders
}
if(rayHits.length > 0){
if(rayHits.length > 1){
let firstDist = Math.abs( rayHits[0].pos.y-curCamPos.y );
if( firstDist<stepUpDist ){
rayHits = [ rayHits[0] ];
}else if( firstDist>stepUpDist && rayHits[0].pos.y > rayHits[1].pos.y && rayHits[1].pos.y > curCamPos.y-this.maxStepHeight ){
return curCamPos;
}
}
this.floorColliderInitialHit=true;
let closestDist=-99999;
let curName;
let curCollisionPos=this.nearestFloorHit;
for(let x=0; x<rayHits.length;++x){
let obj=rayHits[x];
let curHit=obj.pos;
./let curHit=castPos.y-obj.dist;
./let curDist=obj.dist;
let validHit=false;
curName=obj.object.name;
let camDist=curHit.distanceTo( curCamPos );
validHit=camDist<this.maxStepHeight;
./console.log(stepUpDist,camDist, obj.dist, validHit);
if( ( this.portalList[curName] || this.roomWarpZone.includes(curName) ) && validHit){
objHit=curName;
curCollisionPos=curHit;
break;
}else if( !this.itemCheck(curName, validHit) ){
if(camDist<closestDist || objHit==null){
objHit=curName;
closestDist=camDist;
curCollisionPos=curHit;
}
}
}
if( this.nearestFloorObjName==null && objHit!=null){
this.nearestFloorHitPrev=curCollisionPos;
this.nearestFloorObjNamePrev=objHit;
this.nearestFloorHit=curCollisionPos;
this.nearestFloorObjName=objHit;
}
./console.log(this.nearestFloorHitPrev.y-curCollisionPos.y, this.maxStepHeight+this.getStandingHeight() );
if( (this.nearestFloorHitPrev.y-curCollisionPos.y) > (this.maxStepHeight+this.getStandingHeight()) && !this.hasGravity){
this.nearestFloorHit=this.nearestFloorHitPrev;
this.nearestFloorObjName=this.nearestFloorObjNamePrev;
./this.cameraMovement[0] = Math.abs(this.cameraMovement[0])<this.posRotEasingThreshold ? 0 : this.cameraMovement[0]*this.cameraMovementEase;
./this.cameraMovement[1] = Math.abs(this.cameraMovement[1])<this.posRotEasingThreshold ? 0 : this.cameraMovement[1]*this.cameraMovementEase;
if( !objHit ){
curCamPos=this.cameraPrevPos.clone();
}else{
curCamPos=this.cameraPos.clone();
}
objHit=this.nearestFloorObjName;
this.cameraMovement[0] = 0;
this.cameraMovement[1] = 0;
this.pxlDevice.touchMouseData.curFadeOut.multiplyScalar(0);
this.pxlDevice.touchMouseData.velocity.multiplyScalar(0);
./this.pxlDevice.touchMouseData.velocityEase.multiplyScalar(0);
}else{ ./ Player is jumping or falling
this.nearestFloorHitPrev=this.nearestFloorHit;
this.nearestFloorObjNamePrev=this.nearestFloorObjName;
this.nearestFloorHit=curCollisionPos;
this.nearestFloorObjName=objHit;
if( objHit == null ){
this.colliderValid=false;
this.hasGravity=true;
}
}
}else{
./ If this runs, means the user ran off the edge of the collider and there is no floor below them.
this.colliderFail=true;
this.movementBlocked=true;
this.colliderValidityChecked=false;
curCamPos=this.cameraPos.clone();
}
}
}else{
./ User didn't move, may be falling from gravity
./ Call valid object check within distance from camera location
this.colliderValidityChecked=false;
}
this.colliderValidityChecked=false;
return curCamPos;
}
.//////////////////////////////
./ Collider Event Triggers //
.////////////////////////////
./ Should only run when this.colliderValid==false
./ ##
.**
* Checks if the collider is valid.
* For GravitySource, make sure to get vector delta magnitude, not just Y delta
* @param {Vector3} curCamPos - The current camera position.
* @returns {number} - The distance to the collider.
* @example
* ./ Check if the collider is valid, within the max step height.
* let dist=this.pxlCamera.checkColliderValid( this.camera.position );
* ./ Update the camera position.
*.
checkColliderValid( curCamPos ){
this.colliderValidityChecked=true;
let validDist=this.maxStepHeight+this.gravityRate;
let curDist = curCamPos.distanceTo( this.nearestFloorHit );
let checkValid= curDist < validDist;./ && (curCamPos.y+validDist)<this.nearestFloorHit.y;
this.colliderValid=checkValid;
return curDist;
./return checkValid; // Currently not needed, pre-emptive
}
.**
* Triggers an event based on the collider object.
* Currently supports inta-room portals, extra-room warp zones, and only 2 audio triggers.
* Custom triggers haven't been implemented yet,
* For any custom Collider Events, use the `castray()`
* @param {string} [checkObject=null] - The collider object to check.
* @returns {boolean} - Whether an event was triggered.
* @example
* ./ Trigger an event based on the collider object.
* this.pxlCamera.eventTrigger( "AudioTrigger_1" );
* ./ Update the camera position.
* this.setTransform( curPos );
*.
eventTrigger( checkObject=null ){
./ Check if camera is in Roam or Static mode
if( !this.canMove ){ return false; }
./ No collider, might be in a Room Environment with no colliders
if(!checkObject){ return false; }
./ -- Check for Portals -- //
./ -- Within Room Environment position/rotation updates -- //
if(this.portalList[checkObject]){
this.warpEventTriggered( 0, this.portalList[checkObject]);
this.eventCheckStatus=false;
return true;
}
./ -- Check for between Room Warp Zones -- //
./ -- Separate ThreeJS Scene position/rotation updates -- //
if( this.roomWarpZone.includes(checkObject) ){ ./ ## Should live in the Room Environment
this.warpEventTriggered( 1, checkObject );
this.eventCheckStatus=false;
return true; ./ Room warp, kill camera calculations
}
./ -- Check for change between different Collider objects -- //
./ -- Changes may trigger Audio and Screen Visual Effects -- //
this.colliderShiftActive=this.colliderCurObjHit!=checkObject || this.colliderShiftActive ;
this.colliderPrevObjHit= this.colliderCurObjHit;
this.colliderCurObjHit=checkObject;
./ If the volume adjust is mid transition, keep the fade rolling
this.colliderShiftActive=this.colliderShiftActive || !(this.colliderAdjustPerc==1 || this.colliderAdjustPerc==0);
./ Fade Up/Down Audio/Video effects per room names
./ ## These should be handled by the Room Environment itself
if(this.colliderShiftActive && this.colliderCurObjHit){
let volMult= 1;
let audioTriggerCur = this.colliderCurObjHit.includes("AudioTrigger")
./let audioTriggerPrev = (this.colliderPrevObjHit || "").includes("AudioTrigger")
./let audioTrigger= ( audioTriggerCur || audioTriggerPrev ) && ( audioTriggerCur != audioTriggerPrev);
if( audioTriggerCur ){
volMult=-1;
}
this.colliderAdjustPerc=Math.min(1, Math.max(0, this.colliderAdjustPerc + this.colliderAdjustRate * volMult ));
let curExposure=(1-this.colliderAdjustPerc);
let curExp=1.0;
./ ## Convert to function dictionary per Room Environment's Collider[objName]
if(this.colliderCurObjHit == "AudioTrigger_1"){ ./ Sunflower Room
this.pxlEnv.currentAudioZone=1;
curExp= curExp - curExposure*this.uniformScalars.darkBase; ./ ## Don't do it this way... blend, don't add offset
this.uniformScalars.exposureUniformBase=curExp;
}else if(this.colliderCurObjHit == "AudioTrigger_2"){ ./ Lobby
this.pxlEnv.currentAudioZone=2;
let animPerc=1;
curExp= this.uniformScalars.curExp + curExposure*this.uniformScalars.brightBase*animPerc; ./ ## Don't do it this way... blend, don't add offset
this.uniformScalars.exposureUniformBase=curExp;
this.proximityScaleTrigger=true; ./ Fade Out proximity range
this.pxlAudio.setFadeActive(-1);
./}else if(){ } // TODO : Add camera location warp pad check
}else{
this.pxlEnv.currentAudioZone=0;
curExp= curExp*(1-curExposure) + this.uniformScalars.exposureUniformBase*curExposure; ./ ## Don't do it this way... blend, don't add offset
}
./ Transition has completed when True
this.colliderShiftActive=!(this.colliderAdjustPerc==1 || this.colliderAdjustPerc==0);
./ If Lobby geomtry is visible, but no longer in the Lobby, toggle visiblity
./ Runs once, at the moment of collider change
if( this.colliderPrevObjHit=="AudioTrigger_2" && this.colliderCurObjHit!=this.colliderPrevObjHit){
this.proximityScaleTrigger=true; ./ Fade In proximity range
this.pxlAudio.setFadeActive(1);
}
if( this.pxlDevice.mobile ){
curExp=this.colliderAdjustPerc;
}
./ Set scene exposure on post-process composer passes
this.pxlEnv.updateCompUniforms(curExp);
./ Scale proximity visual
if(this.proximityScaleTrigger && !this.pxlDevice.mobile && !this.pxlAutoCam.enabled ){
let proxMult=this.colliderAdjustPerc;
proxMult=1-(1-proxMult)*(1-proxMult);
this.pxlEnv.fogMult.x = proxMult;
if( !this.colliderShiftActive ){
this.proximityScaleTrigger=false;
}
}
this.eventCheckStatus=this.colliderShiftActive;
}
}
.**
* Checks if an item was triggered.
* Currently only used as - if( !this.itemCheck(validHit) ){}
* Preventing standing on the item collider plane
* @param {string} curNameBase - The base name of the item.
* @param {boolean} validHit - Whether the hit was valid.
* @returns {boolean} - Whether the item was triggered.
* @private
*.
itemCheck(curNameBase, validHit){
if(!validHit){ return false; }
let curName=curNameBase.split("_").shift();
if(this.pxlUser.itemListNames.includes(curNameBase)){
let itemPickup=this.pxlUser.checkItemPickUp(curName);
if(itemPickup){
return this.itemActive( curName, curNameBase );
}
}
return false; ./ Allowed to stand on object
}
.**
* Triggers a newly picked-up item.
* If no item is picked up, a random overlay-effect item is selected.
*.
itemTrigger(){
if( this.pxlUser.itemActiveTimer.length>0 ){
this.pxlUser.itemActiveTimer[0]=this.pxlTimer.curMS;
}else{
if( this.pxlUser.mPick.length==0){
this.pxlUser.mPick=this.pxlUtils.randomizeArray( ['LizardKing', 'StarField', 'InfinityZoom'] );
}
./this.pxlUser.mPick="LizardKing";
let setItem= this.pxlUser.mPick.pop();
this.pxlUser.checkItemPickUp(setItem);
this.itemActive( setItem );
}
}
.**
* Activates an item.
* @param {string} [curName=null] - The name of the item.
* @param {string} [curNameBase=null] - The base name of the item.
* @returns {boolean} - Whether the item was activated.
*.
itemActive( curName=null, curNameBase=null ){
if( curName==null ){
return false;
}
let timer=this.pxlTimer.prevMS+this.pxlUser.itemRunTime;
let finCmd="";
let text="";
if(curName=="LowGravity"){
text="Low Gravity";
finCmd="this.lowGrav=0;this.itemGroupList['"+curNameBase+"'].visible=true;";
timer=this.pxlTimer.prevMS+this.pxlUser.itemRunTime;
}else if(curName=="LizardKing"){
text="I am the Lizard King";
finCmd="this.lKing=0;this.lKingWarp.set(...this.lKingInactive);this.lizardKingPass.enabled=false;"+(!this.pxlDevice.mobile && "this.itemGroupList['"+curNameBase+"'].visible=true;");
timer=this.pxlTimer.prevMS+this.pxlUser.itemRunTime;
}else if(curName=="StarField"){
text="Major Tom";
finCmd="this.sField=0;this.starFieldPass.enabled=false;"+(!this.pxlDevice.mobile && "this.itemGroupList['"+curNameBase+"'].visible=true;");
timer=this.pxlTimer.prevMS+this.pxlUser.itemRunTime;
}else if(curName=="InfinityZoom"){
text="Fractal Substrate";
finCmd="this.iZoom=0;this.crystallinePass.enabled=false;"+(!this.pxlDevice.mobile && "this.itemGroupList['"+curNameBase+"'].visible=true;this.pxlEnv.mapComposerWarpPass.needsSwap=true;this.pxlEnv.mapComposerWarpPass.enabled=false;");
timer=this.pxlTimer.prevMS+this.pxlUser.itemRunTime;
./this.pxlEnv.mapComposerWarpPass.needsSwap=false;
this.pxlEnv.mapComposerWarpPass.needsSwap=true;
setTimeout(()=>{
.*if( this.pxlEnv.currentRoom==this.pxlEnv.mainRoom ){
this.pxlEnv.roomComposer.reset();
this.pxlEnv.mapComposer.render();
}else{
this.pxlEnv.mapComposer.reset();
this.pxlEnv.roomComposer.render();
}*.
this.pxlEnv.mapComposer.render();
this.pxlEnv.roomComposer.render();
setTimeout(()=>{
this.pxlEnv.mapComposerWarpPass.needsSwap=false;
this.pxlEnv.mapComposerWarpPass.enabled=true;
.*if( this.pxlEnv.currentRoom==this.pxlEnv.mainRoom ){
this.pxlEnv.roomComposer.reset();
}else{
this.pxlEnv.mapComposer.reset();
}*.
},500);
},500);
.*this.pxlEnv.mapComposer.render();
this.pxlEnv.mapComposerWarpPass.enabled=!this.pxlEnv.mapComposerWarpPass.enabled;
this.pxlEnv.mapComposer.render();*.
./this.pxlEnv.roomComposer.render();
./this.pxlEnv.mapComposer.render();
}else{
return false;
}
this.pxlGuiDraws.buildItemHud(curName,text);
if( !this.pxlDevice.mobile ){
this.pxlUser.itemGroupList[curNameBase].visible=false;
}
this.pxlUser.itemInactiveCmd.push( finCmd );
this.pxlUser.itemActiveTimer.push(timer);
this.pxlUser.itemActiveList.push(text);
return true; ./ Don't stand upon item collision object
}
.///////////////////////////////////
./ Camera Positional Functions //
./////////////////////////////////
./ Flip between free roaming or static camera
./ Run this through pxlNav's trigger system, trigger --
./ pxlNav.trigger("Camera","Roam")
./ -or-
./ pxlNav.trigger("Camera","Static")
.**
* Toggles the camera movement.
* @param {boolean} [canMoveVal=null] - Whether the camera can move.
* @example
* ./ Toggle the camera to `Static` mode.
* this.pxlCamera.toggleMovement( false );
*
* ./ Toggle the camera to `Roam` mode.
* this.pxlCamera.toggleMovement( true );
*.
toggleMovement( canMoveVal=null ){
if(canMoveVal == null){
canMoveVal=!this.canMove;
}
this.canMove = canMoveVal;
this.hasGravity = canMoveVal;
}
.**
* Updates the movement based on the current time.
* Appling down directional key values to camera movement array
*
* Ran internally during the main loop, but revealed for external access
*
* @param {number} curTime - The current time.
* @example
* ./ Update the movement based on the current time.
* this.pxlCamera.updateMovement( this.pxlTimer.deltaTime );
*
* ./ Log the current camera movement direction in X and Z.
* console.log( this.pxlCamera.cameraMovement );
*.
updateMovement(curTime){
./ Check if camera is in Roam or Static mode
if( !this.canMove ){ return; }
if( this.pxlDevice.mobile ){
return;
}
let rate=[0,0];./
let dir=[...this.pxlDevice.directionKeysPressed];
let strafe=0;
let dolly=0;
./ Get millisecond time differences so camera movement is independant of FPS
let deltas=[ (curTime-this.pxlDevice.keyDownCount[0]), (curTime-this.pxlDevice.keyDownCount[1]) ]; ./ 1.000 seconds
./console.log( dir );
./ Array entry
let easingMode = this.mobile ? 1 : 0;
./ Check if either Left or Right direction keys are pressed
./ Default: rate[0] is strafing movement
./ If tank controls are enabled, rate[0] drives rotation rate
./ this.pxlQuality.settings.leftRight == False : Tank Controls; True : Strafing
./ TODO : Yeah, I know. There is a TODO in QualityController.js to decouple movement settings
if((dir[0]+dir[2])==1){
strafe=dir[2]-dir[0];
./ Subtract forward/back from strafing movement to reduce diagonal super-speed
let turnRate=this.pxlQuality.settings.leftRight ? this.cameraEasing[ easingMode ] : ( 1 - Math.min(1, Math.abs(this.cameraMovement[1]*.3)) ) *.5 ;
rate[0]=( (this.pxlQuality.settings.leftRight ? 1.0 : 6.0) + (deltas[0]*(deltas[0])) * .1 ) * turnRate;
rate[0]= Math.min( this.pxlUser.moveSpeed, rate[0] ) * this.movementScalar * this.userInputMoveScalar.x;
}else{
./ Bother Left AND Right direction keys are pressed, cancel movement
this.pxlDevice.keyDownCount[0]=curTime;
}
./ Check if either Up or Down direction keys are pressed
if((dir[1]+dir[3])==1){
dolly=dir[3]-dir[1];
./ Subtract strafing movement from dolly movement to reduce diagonal super-speed
let dollyRate=(1- Math.min(1, Math.abs(this.cameraMovement[0]*.07))) * this.cameraEasing[ easingMode ];
rate[1]=( ((deltas[1]*(deltas[1]*3+2+this.pxlUser.moveSpeed))*.5) ) * dollyRate;
rate[1]= Math.min( this.pxlUser.moveSpeed, rate[1] ) * this.movementScalar * this.userInputMoveScalar.y;
}else{
./ Both Up AND Down direction keys are pressed, cancel movement
this.pxlDevice.keyDownCount[1]=curTime;
}
let moveSpeed = ( rate[0]**2 + rate[1]**2 ) ** 0.5;
this.hasMovementLimit=true;
this.movementMax = 10.0; ./ Meters per second
this.cameraMovement[0]+=strafe*rate[0];
this.cameraMovement[1]+=dolly*rate[1];
}
.**
* Initializes the starting camera position per-frame.
* This is ran in `updateCamera()`
* @returns {Vector3} - The initial camera position.
* @private
*.
initFrameCamPosition(){
let curCamPos=this.cameraPos.clone();
if(!this.cameraBooted){ ./ These should be set from Scene File, if not, initial values
this.cameraAimTarget.position.set(0, 0, 0);./.add(new Vector3(0,0,0));
this.cameraPrevPos = new Vector3(curCamPos.clone());
this.cameraPrevLookAt = new Vector3(0,0,1);
this.hasMoved = true;
this.hasRotated = true;
}else{
let userMovement;
.*if(this.pxlDevice.mobile){ // ## When Mobile is implemented, convert to this.cameraMovement
userMovement=new Vector3(-this.pxlDevice.touchMouseData.curDistance.x*.01,0,-this.pxlDevice.touchMouseData.curDistance.y*.01);
this.cameraMoveLength=userMovement.length();
}else{*.
./userMovement=new Vector3(this.cameraMovement[0],0,this.cameraMovement[1]);
userMovement=new Vector3((this.pxlQuality.settings.leftRight?this.cameraMovement[0]*.5:0),0,this.cameraMovement[1]);
this.cameraMoveLength=userMovement.length();
./}
userMovement.applyQuaternion(this.camera.quaternion);
let moveScalar = this.cameraMoveLength*this.cameraMoveLengthMult;
./ Give some base movement to the camera
./ This way it doesn't ramp up from 0, but from the minimum movement speed
if( moveScalar!=0 ){
let minimumMoveSpeed=0.1;
moveScalar = moveScalar>0 ? Math.max(minimumMoveSpeed,moveScalar) : Math.min(-minimumMoveSpeed,moveScalar);
userMovement.normalize().multiply(new Vector3(1,0,1)).multiplyScalar(moveScalar);
curCamPos.add(userMovement);
this.cameraMovement[0] = Math.abs(this.cameraMovement[0])<this.posRotEasingThreshold ? 0 : this.cameraMovement[0]*this.cameraMovementEase;
this.cameraMovement[1] = Math.abs(this.cameraMovement[1])<this.posRotEasingThreshold ? 0 : this.cameraMovement[1]*this.cameraMovementEase;
if( this.cameraMovement[0] == 0 ){
this.userInputMoveScalar.x=1;
}
if( this.cameraMovement[1] == 0 ){
this.userInputMoveScalar.y=1;
}
this.hasMoved=true;
}
./curCamPos=curCamPos.clone().multiplyScalar(this.camPosBlend).add(this.cameraPrevPos.clone().multiplyScalar(1-this.camPosBlend));
./ ## When GravitySource exists, apply cameraMovement offset
./ Cam movement to Vector3( cm[0], 0, cm[1] ), rotated by Quaternion from Euler Normalize Vector (camPos - collider hit)
./ DO NOT USE CAMERA QUATERNION, movement doesn't align to camera orientation
curCamPos.y=this.cameraPos.y + this.cameraJumpVelocity;
if( this.workerActive ){
this.cameraJumpVelocity=0; ./ Additive from the worker thread
}
}
this.cameraCross=new Vector3(1,0,0).applyQuaternion( this.camera.quaternion );
return curCamPos;
}
.**
* Updates the camera position based on gravity and collisions.
* Delta ( camPos + gravity direction * gravity rate ) > ( Distance camPos to Collider Hit )
*
* Handled internally during the main loop, but revealed for external access
*
* @param {Vector3} curCamPos - The current camera position.
* @returns {Vector3} - The updated camera position.
* @example
* ./ Update the camera position based on gravity and collisions.
* let curPos=this.pxlCamera.updateCamera( this.camera.position );
* curPos = this.pxlCamera.applyGravity( curPos );
*
* ./ Check if the collider is valid, within the max step height.
* let dist=this.pxlCamera.checkColliderValid( curPos );
*
* if( this.colliderValid ){
* ./ Update the camera position.
* this.setTransform( curPos );
* }
*.
applyGravity( curCamPos ){
if( this.canMove && this.hasGravity ){
./curCamPos=this.checkColliderFail( curCamPos );
let validDist=this.maxStepHeight+this.gravityRate;
let jumpUpState=(curCamPos.y)<this.nearestFloorHit.y;
.*if( jumpUpState || this.colliderFail ){
./curCamPos.x = nPrev.x;
./curCamPos.y = Math.max( nPrev.y, curCamPos.y-this.gravityRate );
curCamPos.y = Math.max( 100, curCamPos.y-this.gravityRate );
./curCamPos.z = nPrev.z;
if( curCamPos.y == 100 ){./nPrev.y){
let nPrev=this.nearestFloorHitPrev;
curCamPos=nPrev.clone();
this.resetGravity();
}
}*.
if( jumpUpState ){
let nPrev=this.nearestFloorHitPrev;./.nearestFloorHit;//this.nearestFloorHitPrev;
./curCamPos.x = nPrev.x;
curCamPos.y = Math.max( nPrev.y, curCamPos.y);./-this.gravityRate );
./curCamPos.z = nPrev.z;
if( curCamPos.y < 0 ){./nPrev.y){
curCamPos.x=nPrev.x;./clone();
curCamPos.z=nPrev.z;./clone();
./this.resetGravity();
./this.jumpLanding();
}
}else{
curCamPos.y = Math.max( this.nearestFloorHit.y, curCamPos.y - this.gravityRate );
if( curCamPos.y == this.nearestFloorHit.y && curCamPos.y<this.cameraPrevPos.y ){
this.jumpLanding();
}
}
}else{
let distToFloor=curCamPos.distanceTo( this.nearestFloorHit );
if( distToFloor < this.maxStepHeight){
curCamPos.y = this.nearestFloorHit.y;
}else{
curCamPos=this.cameraPos.clone();
let fallStatus=curCamPos.y > this.nearestFloorHit.y;
this.hasGravity=fallStatus;
this.hasMoved = this.hasMoved || fallStatus;
this.colliderFail= !fallStatus;
this.workerFunc("jumpLanding");
./curCamPos=this.checkColliderFail( curCamPos );
}
}
return curCamPos;
}
.**
* Gets the standing height of the user.
* Head to Foot only - No landing, gravity, or walk-bounce
* @returns {number} - The standing height.
* @example
* ./ Get the standing height of the user.
* let standingHeight=this.pxlCamera.getStandingHeight();
* console.log( "getStandingHeight()", standingHeight );
*.
getStandingHeight(){
let retHeight = this.standingHeight;
if( this.roomStandingHeight.hasOwnProperty(this.pxlEnv.currentRoom) ){
retHeight = this.roomStandingHeight[this.pxlEnv.currentRoom];
}
return retHeight * this.userScale;
}
.**
* Gets the user height including jump and walking-bounce offsets.
* @returns {number} - The user height.
* @example
* ./ Get the user height including jump and walking-bounce offsets.
* let userHeight=this.pxlCamera.getUserHeight();
* console.log( "getUserHeight()", userHeight );
*.
getUserHeight(){
./ Add bob to movement to appear as taking steps
let walkBounceAdd=Math.min(1, Math.abs(this.cameraMovement[1]));
this.walkBouncePerc=this.walkBouncePerc>=1?1:this.walkBouncePerc + this.walkBounceEaseIn * walkBounceAdd;
this.walkBounce+=walkBounceAdd * this.walkBounceRate;
this.walkBouncePerc=this.walkBouncePerc * this.walkBounceEaseOut + walkBounceAdd;
if(this.walkBouncePerc<.03){
this.walkBouncePerc=0;
this.walkBounce=0;
this.walkBounceSeed=Math.random()*2351.3256;
}
./let walkBounceOffset=Math.sin(this.walkBounce*.4+this.walkBounceSeed+this.cameraMovement[1]*.2)*this.walkBouncePerc*.3;
let walkBounceOffset=Math.sin(this.walkBounce+this.walkBounceSeed) * this.walkBouncePerc * this.walkBounceHeight;
let curStandingHeight=this.getStandingHeight() - this.standingHeightGravInfluence + walkBounceOffset;
return curStandingHeight;
}
.///////////////////////////////////
./ Camera Rotational Functions //
./////////////////////////////////
.**
* Applies mobile rotation to the camera.
* Mobile currently doesn't support movement in Rooms
*
* Currently unused; awaiting mobile-gyroscope implementation
*.
camApplyMobileRotation(){
if(this.cameraPose.alpha!=null){
let dtor=0.017453292519943278; ./ PI/180
let halfSqrt=2.23606797749979; ./ Sqrt(5)
let camPoseQuat=new Quaternion();
let a=this.cameraPose.alpha*dtor+this.cameraPose.alphaOffset+2.1;
let b=this.cameraPose.beta*dtor;
let g=this.cameraPose.gamma*dtor;
let viewNormal=new Vector3(0,0,1);
let poseQuat=new Quaternion();
let initPoseQuat=new Quaternion(-halfSqrt,0,0,halfSqrt);
let euler=new Euler();
euler.set(b,a,-g,'YXZ'); ./ Device returns YXZ for deviceOrientation
camPoseQuat.setFromEuler(euler);
camPoseQuat.multiply(initPoseQuat);
camPoseQuat.multiply(poseQuat.setFromAxisAngle(viewNormal,-this.cameraPose.orientation));
camPoseQuat.normalize();
let smoothedQuat=new Quaternion();
Quaternion.slerp(this.camera.quaternion,camPoseQuat,smoothedQuat,0.35);
let cameraLimit = new Euler().setFromQuaternion(smoothedQuat);
cameraLimit.x = Math.max(-0.95 * Math.PI . 2, Math.min(0.95 * Math.PI / 2, cameraLimit.x));
smoothedQuat.setFromEuler(cameraLimit);
this.camera.setRotationFromQuaternion(smoothedQuat);
this.hasRotated=true;
}
}
.**
* Updates the camera rotation; Look At(Aim) Target
*
* Note: Known bug, static camera rotation is based on the current camera rotation
* This causes an inate rotation when the camera is moved to a new position
*.
updateRoamRotation(){
if(this.cameraPose.alpha==null){ ./ ## Should gyro exist, don't run. But need to allow controlled look around on mobile
let xGrav=this.pxlDevice.gyroGravity[2];./*this.gravityRate;//*PI;
let viewNormal=new Vector3(0,0,1);
let poseQuat=new Quaternion();
./ ## Theres a better place for this....
this.pxlDevice.touchMouseData.velocity.y=Math.min(this.touchSensitivityLimits, Math.max(-this.touchSensitivityLimits, this.pxlDevice.touchMouseData.velocity.y));
let euler=new Euler();
let camPoseQuat;
if( this.pxlDevice.mobile ){
let invertLookBool = this.pxlOptions.userSettings.look.mobile.invert || false;
let invertLook = invertLookBool ? -1 : 1;
euler.set(
this.pxlDevice.touchMouseData.netDistance.y.this.pxlDevice.sH*4 * invertLook,
this.pxlDevice.touchMouseData.netDistance.x.this.pxlDevice.sW*7 * invertLook + xGrav,
0,
'YXZ'
); ./ Device returns YXZ for deviceOrientation
camPoseQuat=new Quaternion();
camPoseQuat.setFromEuler(euler);
camPoseQuat=this.pxlDevice.touchMouseData.initialQuat.clone().multiply(camPoseQuat);
./camPoseQuat.multiply(poseQuat.setFromAxisAngle(viewNormal,-this.cameraPose.orientation));
}else{
let invertLookBool = this.pxlOptions.userSettings.look.pc.invert || false;
let invertLook = invertLookBool ? -1 : 1;
euler.set(
this.pxlDevice.touchMouseData.velocity.y*.005 * invertLook,
this.pxlDevice.touchMouseData.velocity.x*.008 * invertLook + xGrav,
0,
'YXZ'./ Device returns YXZ for deviceOrientation
);
camPoseQuat=new Quaternion();
camPoseQuat.setFromEuler(euler);
./camPoseQuat=this.pxlDevice.touchMouseData.initialQuat.clone().multiply(camPoseQuat);
camPoseQuat=this.camera.quaternion.clone().multiply(camPoseQuat);
}
camPoseQuat.normalize();
let lookAt= new Vector3(0,0,-10).applyQuaternion( camPoseQuat ).add( this.camera.position );
this.camera.setRotationFromQuaternion(camPoseQuat);
this.camera.lookAt(lookAt);
this.camera.up.set( 0,1,0 );
this.hasRotated=true;
}
}
updateStaticRotation(){
./ this.pxlDevice.touchMouseData.startPos;
./ this.pxlDevice.touchMouseData.endPos;
./ this.pxlDevice.touchMouseData.netDistance;
let blendOut=1;
if( this.touchBlender ){
./ Camera rotation easing logic-
blendOut=Math.min(1, Math.max(0, this.pxlTimer.curMS - this.pxlDevice.touchMouseData.releaseTime ));
blendOut*=blendOut;
this.pxlDevice.touchMouseData.netDistance.multiplyScalar(1-blendOut);
this.touchBlender=blendOut<1;
}else{
this.pxlDevice.touchMouseData.netDistance.multiplyScalar(.5);
}
let euler=new Euler();
euler.set(
(this.pxlDevice.touchMouseData.netDistance.y.this.pxlDevice.sH*2),
(this.pxlDevice.touchMouseData.netDistance.x.this.pxlDevice.sW*2),
0,
'YXZ'
); ./ Device returns YXZ for deviceOrientation
./ Limit Up/Down looking
let camPoseQuat=new Quaternion().clone( this.camera.quaternion );
camPoseQuat.setFromEuler(euler);
camPoseQuat=this.camera.quaternion.clone().multiply(camPoseQuat);
./camPoseQuat.multiply(poseQuat.setFromAxisAngle(viewNormal,-this.cameraPose.orientation));
camPoseQuat.normalize();
./ let smoothedQuat=new Quaternion();
if( this.touchBlender ){
camPoseQuat.slerp(this.camera.quaternion.clone(),blendOut).normalize();
}
let lookAt= new Vector3(0,0,-10).applyQuaternion( camPoseQuat ).add( this.camera.position );
this.camera.setRotationFromQuaternion(camPoseQuat);./smoothedQuat);
this.camera.lookAt(lookAt);
this.camera.up.set( 0,1,0 );
this.hasRotated=true;
}
.**
* Locks the camera to look at a target.
* @example
* ./ Lock the camera to look at a target.
* let targetObj = new Object3D();
* targetObj.position.set( 10, 15, 5 );
*
* this.lookAtLockPerc=1;
* this.pxlCamera.lookAtTarget( targetObj );
*.
lookAtTargetLock(){
if(!this.lookAtTargetActive){ return; }
if(this.lookAtTargetActive){
if(this.lookAtLockFader!=0){
this.lookAtLockPerc+=(this.lookAtLockFader+Math.min(1,this.pxlDevice.touchMouseData.velocity.length()*.001))*this.lookAtLockFadeRate;
if(this.lookAtLockPerc<0 || this.lookAtLockPerc>1){
this.lookAtLockPerc=this.lookAtLockPerc<0?0:1;
this.lookAtLockFader=0;
}
this.lookAtPerc.x = this.lookAtLockPerc;
}
./ If Look At is locked
./ set the offset in rotation
./ slerpin some quats!
if(this.lookAtLockPerc>0){
let origCamQuat=this.camera.quaternion.clone();
this.camera.lookAt(this.cameraAimTarget.position);
let targetCamQuat=this.camera.quaternion.clone();
if(this.lookAtLockPerc==1){
this.camera.setRotationFromQuaternion( targetCamQuat );
}else{
this.camera.setRotationFromQuaternion( targetCamQuat.slerp(origCamQuat,Math.cos(this.lookAtLockPerc*pi)*.5+.5) );
}
this.hasRotated=true;
}
}
}
.//////////////////////////////////////////
./ Render Effects / Quality Functions //
.////////////////////////////////////////
.**
* Triggers a Room Warp . Portal Screen Effect event.
* This will initiate the visual effect and set the end warp object and target.
* The actual warp will be run in the `warpCamRun()` function on following frames.
* @param {number} [visualType=0] - The type of visual effect.
* @param {Object} [warpObj=null] - The warp object.
* @param {string} [target='init'] - The target of the warp.
* @example
* ./ Trigger a Room Warp / Portal Screen Effect event.
* this.pxlCamera.warpEventTriggered( 1, warpObj, 'init' );
*.
warpEventTriggered( visualType=0, warpObj=null, target='init' ){
if( !this.warpActive ){
this.pxlEnv.mapComposerWarpPass.needsSwap=true;
this.warpType=visualType;
this.warpObj=warpObj;
this.warpTarget=target;
this.warpActive=true;
this.pxlEnv.initWarpVisual( visualType );
}
}
.**
* Runs the room warping camera effects.
*.
warpCamRun(){
if(this.warpType==0){
this.setToObj( this.warpObj );
}else if(this.warpType==1){
let init=this.warpTarget=='init';
this.warpToRoom( this.warpObj, init, this.warpTarget );
}
this.pxlEnv.setExposure( this.uniformScalars.exposureUniformBase );
this.warpObj=null;
this.warpTarget=null;
this.warpActive=false;
}
.**
* Updates low-quality render events.
* @private
*.
lowQualityUpdates(){
if(this.HDRView){
let uPitch=new Vector3(0,0,-1).applyQuaternion( this.camera.quaternion );
let uRot=uPitch.clone().multiply(new Vector3(1,0,1)).normalize();
let ptr=0.1591549430918955;
./ Update shader uniforms -
this.camRotPitch.set(
-Math.atan2(uRot.x,uRot.z)*ptr,
uPitch.y*.5 );
}
}
.**
* Updates mid-quality render events.
* @private
*.
midQualityUpdates(){
./ Trailing Effects; Fake Motion Blur
if( this.pxlQuality.settings.motion ){ ./ Don't run blur pass if the quality setting is under 50%
let shaderCamRot=new Vector3(0,0,0);
shaderCamRot.applyQuaternion(this.camera.quaternion);./.add(camOrigQuat).multiplyScalar(.5);
this.camRotXYZ.multiplyScalar(.8).add( shaderCamRot.multiplyScalar(.2) );
let viewDirection;
if(this.pxlDevice.mobile){
let sWHalf = sW*.5
let sHHalf= sH*.5;
let fromWorldPos=new Vector3(0,0,10);
let toWorldPos=new Vector3(0,0,10);
./fromWorldPos.applyMatrix4( this.camera.matrixWorld.clone() ).project(this.camera);
./toWorldPos.applyMatrix4( this.prevWorldMatrix ).project(this.camera);
fromWorldPos.applyQuaternion( this.camera.quaternion.clone() ).project(this.camera);
toWorldPos.applyQuaternion( this.prevQuaternion ).project(this.camera);
fromWorldPos.x=(fromWorldPos.x+1)*sWHalf;
fromWorldPos.y=-(fromWorldPos.y-1)*sHHalf;
toWorldPos.x=(toWorldPos.x+1)*sWHalf;
toWorldPos.y=-(toWorldPos.y-1)*sHHalf;
viewDirection=toWorldPos.clone().sub(fromWorldPos.clone()).multiplyScalar(.6).multiply(new Vector3(this.pxlDevice.screenRes.x,this.pxlDevice.screenRes.y,0));
let motionBlurMaxDist=.1;
if(viewDirection.length>motionBlurMaxDist){
viewDirection.normalize().multiplyScalar(motionBlurMaxDist);
}
./viewDirection=this.pxlDevice.touchMouseData.velocityEase.clone().multiplyScalar( Math.max(this.screenRes[1],this.screenRes[0]) );
}else{
./viewDirection=this.pxlDevice.touchMouseData.velocityEase.clone().multiplyScalar( Math.max(this.pxlDevice.screenRes.x,this.pxlDevice.screenRes.y) );
viewDirection=this.pxlDevice.touchMouseData.velocity.clone().multiplyScalar( Math.max(this.pxlDevice.screenRes.x,this.pxlDevice.screenRes.y) );
}
let toDir=new Vector2( viewDirection.x, -viewDirection.y);
let blurDir=new Vector2(0,0).lerpVectors( this.pxlEnv.blurDirPrev, toDir, .85 );
./ Update motionBlur direction uniforms -
this.pxlEnv.blurDirPrev.set( this.pxlEnv.blurDirCur );
this.pxlEnv.blurDirCur.set( blurDir );
}
}
.//////////////////////////////////////////////////////
./ WebSocket Emit for Position and Rotation Changes //
.////////////////////////////////////////////////////
./ Notify Server of Position / Rotation Changes
.**
* Emits camera transforms to the server.
* NETWORKING HAS BEEN REMOVED, you'll need to implement your own server-side logic.
* @param {Vector3} cameraPos - The camera position.
* @param {number} standingHeight - The standing height.
* @param {boolean} [force=false] - Whether to force the emission.
* @private
*.
emitCameraTransforms( cameraPos, standingHeight, force=false ){
this.emit("cameraTransforms",{
cameraPos:cameraPos,
standingHeight:standingHeight,
force:force
});
./ Networking scripting removed
}
.**
* Jog the server memory with the current camera position.
* Originally the server would use this event to update remote client's positions of the local user.
* Also as a server-side sanity check that the user is in the correct "chat" room and other data that may have gone stale.
* You'll need to implement your own usage of this function with server-side logic.
* CURRENTLY UNUSED
* @private
*.
jogServerMemory(){
let curCamPos=this.cameraPos.clone();
let standingHeight=this.getUserHeight();
this.emitCameraTransforms( curCamPos, standingHeight, true );
}
.//////////////////////////////////
./ Main Camera Update Function //
.////////////////////////////////
.**
* Main update function for the camera.
*.
updateCamera(){
./this.updateStaticRotation();
./let velEaseMag=this.pxlDevice.touchMouseData.velocityEase.length();
let velEaseMag = this.pxlDevice.touchMouseData.velocity.length();
this.hasRotated = this.hasRotated || velEaseMag > 0;
this.camUpdated = this.camUpdated || this.hasRotated;
./ Fade out touchMouseData, likely to be removed in later versions
this.pxlDevice.touchMouseData.curFadeOut.multiplyScalar( .7 );
./ Check if the camera has been updated; step camera values, apply gravity, check for colliders, and interact with objects
if( this.camUpdated ){ ./ || this.pxlDevice.touchMouseData.active){// || this.lookAtLockPerc>0 ){ // ## Not using any cam locking yet
./ Camera checks are initiating
this.camUpdated=false;
let didUpdate=false;
this.updateDeviceValues( velEaseMag );
./ TODO : Enable when User class is updated
./this.pxlUser.localUserTurned=this.pxlDevice.touchMouseData.velocity.length() == 0;
this.prevQuaternion.copy( this.camera.quaternion );
./this.prevWorldMatrix.set( this.camera.matrixWorld ); // Only used if running higher quality motion blur, not needed
./ For Gyro enabled devices; Mobile / Tablet / Surface devices
./ ## Work in progress, waste of calculations acurrently
./this.camApplyMobileRotation();
let cameraPos=this.initFrameCamPosition();
./ Appy Gravity Height Offset
let standingHeight=this.getUserHeight();
if( this.pxlOptions.staticCamera ){./ Static Camera Mode
this.hasGravity = false;
this.hasMoved = false;
if( this.pxlOptions.allowStaticRotation && this.hasRotated ){ ./ Static Camera Mode
this.updateStaticRotation();
this.camera.updateMatrixWorld();
}
this.hasRotated = false;
return;
}
./ Movement checks
if( this.hasMoved || this.hasGravity ){./&& this.canMove ){
./ Check if the camera is in a valid position
cameraPos=this.colliderCheck( cameraPos );
./ If in air, gravity grows
./ This only updates gravity prior to jump calculations
./ User vertical based calculations are ran in `applyGravity()`
this.updateGravity( this.pxlTimer.deltaTime );
./ When Jumping / Falling, Collion Hit Position and Object are marked as Invalid
if( !this.colliderValid && !this.colliderValidityChecked ){
./ Check if within maxStepHeight+gravityRate distance of collider hit position
this.jump=this.checkColliderValid( cameraPos ); ./ Sets colliderValid
}else{
this.jump=0;
}
this.eventCheckStatus=true;
./ Apply gravity rate to current camera position
cameraPos=this.applyGravity(cameraPos);
./ Check length of Camera Movement, `**.5` is the square root of the sum of the squares
./ TODO : Implement when User class is updated
./this.pxlUser.localUserMoved= this.hasGravity || ((this.cameraMovement[0]**2 + this.cameraMovement[1]**2) ** .5) > 0;
this.cameraPrevPos=this.cameraPos.clone();
this.cameraPos=cameraPos.clone();
didUpdate = this.cameraPos.distanceTo(this.cameraPrevPos) > 0;
cameraPos.y+=standingHeight+this.cameraJumpHeight;
this.camera.position.copy(cameraPos);
}
./ Performing different rotation logic based on if the camera is in Roam or Static mode
./ Roaming orients the camera up in Y
./ Static keeps the camera oriented with Camera Position -to- LookAt cross product
./ `cross( cross( normalized(LookAt-Pos), Up), Up )`
if( this.hasRotated && this.canMove ){ ./ Roam Camera Mode
this.updateRoamRotation();
didUpdate = didUpdate || this.hasRotated;
}else if( this.hasRotated ){ ./ Static Camera Mode
this.updateStaticRotation();
didUpdate = didUpdate || this.hasRotated;
}
./this.lookAtTargetLock(); // Camera lookAt target locking
if( didUpdate ){
this.camera.updateMatrixWorld(); ./ ## Only needed for lobby geo... Fix
this.emitCameraTransforms( cameraPos, standingHeight );
}
./ Calculations completed, reset flags
this.hasMoved = false;
this.hasRotated = false;
this.cameraBooted=true;
}.*else{
./ TODO : User class still not updated to utilize these
this.pxlUser.localUserMoved=false;
this.pxlUser.localUserTurned=false;
}*.
}
./ -- -- --
./ pxlNav Callbacks
./ `event` should be of `pxlEnum.CAMERA_EVENT` type
.**
* Subscribes to a camera event.
* @param {string} event - The camera event.
* @param {function} callback - The callback function.
* @example
* ./ Subscribe to a camera event.
* this.pxlCamera.subscribe( "cameraTransforms", (data) => {
* console.log( data );
* }
*.
subscribe( event, callback ){
if( !this.callbacks.hasOwnProperty(event) ){
this.callbacks[event] = [];
}
this.callbacks[event].push( callback );
}
.**
* Emits a camera event.
* @private
*.
emit( event, data ){
if( this.callbacks.hasOwnProperty(event) ){
this.callbacks[event].forEach( (callback) => {
callback( data );
});
}
}
./ -- -- --
}