import {VRSPACEUI} from '../vrspace-ui.js';
/**
Portal is an entrance to other worlds, disabled by default.
*/
export class Portal {
/** Create a portal
@param scene babylonjs scene
@param serverFolder containing world class and content
@param callback to execute when portal is activated (clicked, tapped)
@param shadowGenerator optionally, portal can cast shadows
*/
constructor( scene, serverFolder, callback, shadowGenerator ) {
this.scene = scene;
this.serverFolder = serverFolder;
this.callback = callback;
this.name = serverFolder.name;
this.subTitle = null;
this.alwaysShowTitle = false;
this.imageUrl = null;
if ( serverFolder.relatedUrl() ) {
this.imageUrl = serverFolder.relatedUrl();
this.thumbnail = new BABYLON.Texture(this.imageUrl);
}
this.shadowGenerator = shadowGenerator;
this.isEnabled = false;
this.angle = 0;
// used in dispose:
this.controls = [];
this.textures = [];
this.materials = [];
this.soundUrl = VRSPACEUI.contentBase+"/babylon/portal/couchhero_portal-idle.mp3";
this.soundDistance = 5;
this.soundVolume = .5;
}
/** handy, returns base url and folder name */
worldUrl() {
return this.serverFolder.baseUrl+this.serverFolder.name;
}
/** dispose of everything */
dispose() {
this.playSound(false);
if (this.sound) {
this.sound.dispose();
}
this.group.dispose();
if (this.thumbnail) {
this.thumbnail.dispose();
}
this.material.dispose();
for ( var i = 0; i < this.controls.length; i++ ) {
// CHECKME doesn's seem required
this.controls[i].dispose();
}
for ( var i = 0; i < this.textures.length; i++ ) {
this.textures[i].dispose();
}
for ( var i = 0; i < this.materials.length; i++ ) {
this.materials[i].dispose();
}
if ( this.pointerTracker ) {
this.scene.onPointerObservable.remove(this.pointerTracker);
delete this.pointerTracker;
}
}
/** Load and display portal at given coordinates. Copies existing portal mesh to new coordinates and angle.
@param x
@param y
@param z
@param angle
*/
async loadAt(x,y,z,angle) {
this.angle = angle;
this.group = new BABYLON.TransformNode('Portal:'+this.name);
this.group.position = new BABYLON.Vector3(x,y,z);
this.group.rotationQuaternion = new BABYLON.Quaternion.RotationAxis(BABYLON.Axis.Y,angle);
this.group.Portal = this;
if (this.shadowGenerator) {
var clone = VRSPACEUI.portal.clone();
clone.parent = this.group;
var meshes = clone.getChildMeshes();
for ( var i = 0; i < meshes.length; i++ ) {
this.shadowGenerator.getShadowMap().renderList.push(meshes[i]);
}
} else {
VRSPACEUI.copyMesh(VRSPACEUI.portal, this.group);
}
var plane = BABYLON.Mesh.CreatePlane("PortalEntrance:"+this.name, 1.60, this.scene);
plane.parent = this.group;
plane.position = new BABYLON.Vector3(0,1.32,0);
this.pointerTracker = (e) => {
if(e.type == BABYLON.PointerEventTypes.POINTERDOWN){
var p = e.pickInfo;
if ( p.pickedMesh == plane ) {
if ( this.isEnabled ) {
console.log("Entering "+this.name);
this.enter();
} else {
console.log("Not entering "+this.name+" - disabled");
}
}
}
};
this.scene.onPointerObservable.add(this.pointerTracker);
this.material = new BABYLON.StandardMaterial(this.name+"-noise", this.scene);
plane.material = this.material;
this.material.disableLighting = true;
this.material.backFaceCulling = false;
var noiseTexture = new BABYLON.NoiseProceduralTexture(this.name+"-perlin", 256, this.scene);
this.material.lightmapTexture = noiseTexture;
noiseTexture.octaves = 4;
noiseTexture.persistence = 1.2;
noiseTexture.animationSpeedFactor = 2;
plane.visibility = 0.85;
this.textures.push( noiseTexture );
this.title = BABYLON.MeshBuilder.CreatePlane("Text:"+this.name, {height:2,width:4}, this.scene);
this.title.parent = this.group;
this.title.position = new BABYLON.Vector3(0,2.5,0);
this.title.isVisible = this.alwaysShowTitle;
var titleTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateForMesh(this.title, 256,256);
this.materials.push(this.title.material);
this.titleText = new BABYLON.GUI.TextBlock();
this.titleText.color = "white";
this.showTitle();
titleTexture.addControl(this.titleText);
//this.controls.push(titleText); // CHECKME doesn's seem required
this.textures.push(titleTexture);
this.attachSound();
return this;
}
attachSound() {
if ( this.soundUrl ) {
this.sound = new BABYLON.Sound(
"portalSound:"+this.name,
this.soundUrl,
this.scene, null, {
loop: true,
autoplay: false,
spatialSound: true,
streaming: false,
distanceModel: "linear",
maxDistance: this.soundDistance, // default 100, used only when linear
panningModel: "equalpower" // or "HRTF"
});
this.sound.attachToMesh(this.group);
this.sound.setVolume(this.soundVolume);
}
}
playSound(enable) {
if ( this.sound ) {
if ( enable ) {
this.sound.play();
// chrome hacks
BABYLON.Engine.audioEngine.audioContext?.resume();
BABYLON.Engine.audioEngine.setGlobalVolume(1);
} else if ( this.sound ) {
this.sound.stop();
}
}
}
showTitle() {
if ( this.titleText ) {
if ( this.subTitle) {
this.titleText.text = this.name.toUpperCase()+'\n'+this.subTitle;
} else {
this.titleText.text = this.name;
}
}
}
setTitle(title) {
this.subTitle = title;
this.showTitle();
}
getTitle() {
return this.subTitle;
}
/** Enables or disables the portal
@param enable
*/
enabled(enable) {
if ( enable ) {
this.material.emissiveTexture = this.thumbnail;
} else {
this.material.emissiveTexture = null;
}
this.title.isVisible = enable || this.alwaysShowTitle;
this.isEnabled = enable;
this.playSound(enable);
}
/** Executes callback on entry */
enter() {
if ( this.callback ) {
this.callback(this);
}
}
}