UNPKG

@vrspace/babylonjs

Version:

vrspace.org babylonjs client

226 lines (216 loc) 7.08 kB
import { ServerFolder } from '../../core/server-folder.js'; import { VRSPACEUI } from '../vrspace-ui.js'; import { TextArea } from '../widget/text-area.js'; /** Portal is an entrance to other worlds, disabled by default. */ export class Portal { /** Create a portal @param scene babylonjs scene @param {ServerFolder} 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; /** @type {ServerFolder} */ this.serverFolder = serverFolder; this.callback = callback; this.name = serverFolder.name; this.description = null; 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.isNearPickable = VRSPACEUI.allowHands; 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.showTitle(); 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) { /* TODO audio V2 VRSPACEUI.audioEngine.audioContext?.resume(); VRSPACEUI.audioEngine.setGlobalVolume(1); */ 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.title && (this.isEnabled || this.alwaysShowTitle)) { this.title = new TextArea(this.scene, this.name, this.name); this.title.autoScale = true; this.title.addHandles = false; this.title.addBackground = false; this.title.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER; this.title.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP; this.title.size = 0.1; //this.title.group.parent = this.group; this.title.position = new BABYLON.Vector3(0, 2.5, 0); //this.title.isVisible = this.alwaysShowTitle; // TODO this.title.text = ""; if (this.subTitle) { this.title.text += this.subTitle+"\n"; } if (this.description) { this.title.text += this.description; } this.title.show(); this.title.group.parent = this.group; } } setTitle(title) { this.subTitle = title; this.clearTitle(); this.showTitle(); } getTitle() { return this.subTitle; } clearTitle() { if (this.title) { this.title.dispose(); this.title == null; } } /** Enables or disables the portal @param enable */ enabled(enable) { this.isEnabled = enable; if (enable) { this.material.emissiveTexture = this.thumbnail; this.showTitle(); } else { this.material.emissiveTexture = null; this.clearTitle(); } this.playSound(enable); } /** Executes callback on entry */ enter() { if (this.callback) { this.callback(this); } } }