UNPKG

@vrspace/babylonjs

Version:

vrspace.org babylonjs client

279 lines (254 loc) 9.13 kB
import { Avatar } from './avatar.js'; import { CameraHelper } from '../core/camera-helper.js'; import { MediaHelper } from '../core/media-helper.js'; import { VRSPACEUI } from "../ui/vrspace-ui.js"; /** A disc that shows video stream. Until streaming starts, altText is displayed on the cylinder. It can be extended, and new class provided to WorldManager factory. */ export class VideoAvatar extends Avatar { constructor( scene, callback, customOptions ) { super(scene); this.video = true; this.callback = callback; this.deviceId = null; this.radius = 1; this.altText = "N/A"; this.altImage = null; this.textStyle = "bold 64px monospace"; this.textColor = "black"; this.backColor = "white"; this.maxWidth = 640; this.maxHeight = 640; /** Should show() start video? */ this.autoStart = true; /** Should own video avatar be attached to hud? */ this.autoAttach = true; this.attached = false; this.displaying="NONE"; if ( customOptions ) { for(var c of Object.keys(customOptions)) { this[c] = customOptions[c]; } } } /** Show the avatar. Used for both own and remote avatars. */ async show() { if ( ! this.mesh ) { if ( this.autoAttach ) { this.cameraTracker = () => this.cameraChanged(); } this.mesh = BABYLON.MeshBuilder.CreateDisc("VideoAvatar", {radius:this.radius}, this.scene); //mesh.visibility = 0.95; this.mesh.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL; this.mesh.position = new BABYLON.Vector3( 0, this.radius, 0); this.mesh.material = new BABYLON.StandardMaterial("WebCamMat", this.scene); this.mesh.material.emissiveColor = new BABYLON.Color3.White(); this.mesh.material.specularColor = new BABYLON.Color3.Black(); // used for collision detection (3rd person view) this.mesh.ellipsoid = new BABYLON.Vector3(this.radius, this.radius, this.radius); this.mesh.avatar = this; // CHECKME // glow layer may make the texture invisible, needd to turn of glow for the mesh if ( this.scene.effectLayers ) { this.scene.effectLayers.forEach( (layer) => { if ( 'GlowLayer' === layer.getClassName() ) { layer.addExcludedMesh(this.mesh); } }); } // display alt text before video texture loads: this.displayAlt(); if ( this.autoStart ) { await this.displayVideo(); } } } /** dispose of everything */ dispose() { super.dispose(); if ( this.mesh ) { if ( this.mesh.parent && !this.attached) { // if attached, this could dispose of camera // otherwise dispose of parent mesh created by avatar loader this.mesh.parent.dispose(); } if ( this.mesh.material ) { if ( this.mesh.material.diffuseTexture ) { this.mesh.material.diffuseTexture.dispose(); } this.mesh.material.dispose(); } this.mesh.dispose(); delete this.mesh; } if ( this.cameraTracker ) { CameraHelper.getInstance(this.scene).removeCameraListener(this.cameraTracker); } this.attached = false; } isEnabled() { return Object.hasOwn(this,"mesh"); } /** Display and optionally set altText. */ displayAltText(text) { this.displaying="TEXT"; if ( text ) { this.altText = text; } if ( this.mesh.material.diffuseTexture ) { this.mesh.material.diffuseTexture.dispose(); } this.mesh.material.diffuseTexture = new BABYLON.DynamicTexture("WebCamTexture", {width:128, height:128}, this.scene); this.mesh.material.diffuseTexture.drawText(this.altText, null, null, this.textStyle, this.textColor, this.backColor, false, true); } /** Display and optionally set altImage @param image path to the image file */ displayImage(image) { this.displaying="IMAGE"; if ( image ) { this.altImage = image; } if ( this.mesh.material.diffuseTexture ) { this.mesh.material.diffuseTexture.dispose(); } this.mesh.material.diffuseTexture = new BABYLON.Texture(this.altImage, this.scene, null, false); } /** Displays altImage if available, altText otherwise */ displayAlt() { if ( this.altImage ) { this.displayImage(); } else { this.displayAltText(); } } /** Display video from given device, used for own avatar. */ async displayVideo(deviceId) { if ( this.displaying === "VIDEO" ) { return; } this.deviceId = MediaHelper.selectVideoInput(deviceId); if ( this.deviceId ) { BABYLON.VideoTexture.CreateFromWebCamAsync(this.scene, { maxWidth: this.maxWidth, maxHeight: this.maxHeight, deviceId: this.deviceId }).then( (texture) => { if ( this.mesh.material.diffuseTexture ) { this.mesh.material.diffuseTexture.dispose(); } this.mesh.material.diffuseTexture = texture; this.displaying="VIDEO"; if ( this.callback ) { this.callback(); } }); } } /** Create and display VideoTexture from given MediaStream. */ displayStream( mediaStream ) { if ( mediaStream ) { // CHECKME: otherwise error? BABYLON.VideoTexture.CreateFromStreamAsync(this.scene, mediaStream).then( (texture) => { if ( this.mesh.material.diffuseTexture ) { this.mesh.material.diffuseTexture.dispose(); } this.mesh.material.diffuseTexture = texture; this.displaying="STREAM"; }); } } /** Rescale own avatar and attach to current camera at given position @param position where to put the avatar, by default it goes to the top left corner */ attachToCamera( position ) { if (this.mesh && !this.attached) { this.mesh.billboardMode = BABYLON.Mesh.BILLBOARDMODE_NONE; this.mesh.parent = this.camera; if ( position ) { this.mesh.position = position; } else { this.windowResized = () => this.moveToCorner(); window.addEventListener("resize", this.windowResized); this.moveToCorner(); var scale = (this.radius/2)/20; // 5cm size this.mesh.scaling = new BABYLON.Vector3(scale, scale, scale); } this.attached = true; this.cameraChanged(); CameraHelper.getInstance(this.scene).addCameraListener(this.cameraTracker); } } /** * @private */ moveToCorner() { if ( this.mesh ) { this.mesh.position = new BABYLON.Vector3(-VRSPACEUI.hud.scaling() * .2 * this.scene.getEngine().getAspectRatio(this.scene.activeCamera) + 0.05, - VRSPACEUI.hud.vertical(), 0.5); } } /** Rescale own avatar and detach from camera */ detachFromCamera() { if ( this.attached && this.mesh ) { if ( this.windowResized ) { window.removeEventListener("remove", this.windowResized); delete this.windowResized; } this.mesh.billboardMode = BABYLON.Mesh.BILLBOARDMODE_Y; this.mesh.position = this.camera.position; // CHECKME: must be the same console.log("Mesh position: "+this.mesh.position); this.mesh.scaling = new BABYLON.Vector3(1, 1, 1); CameraHelper.getInstance(this.scene).removeCameraListener(this.cameraTracker); this.mesh.parent = null; this.attached = false; } } /** Called when active camera changes/avatar attaches to camera */ cameraChanged() { if ( this.autoAttach && this.attached && this.mesh ) { console.log("Camera changed: "+this.scene.activeCamera.getClassName()+" new position "+this.scene.activeCamera.position); if ( this.scene.activeCamera.getClassName() == 'UniversalCamera' ) { this.camera = this.scene.activeCamera; this.attached = true; this.mesh.parent = this.camera; } } } getUrl() { return "video"; } basePosition() { if ( this.mesh.parent ) { // CHECKME this is used by avatarController and world serializer return new BABYLON.Vector3(this.mesh.parent.position.x, this.mesh.parent.position.y, this.mesh.parent.position.z); } return new BABYLON.Vector3(this.mesh.position.x, this.mesh.position.y-this.radius, this.mesh.position.z); } topPositionRelative() { return new BABYLON.Vector3(0, this.userHeight-this.radius, 0); } baseMesh() { return this.mesh; } /** Remote emoji event routed by WorldManager. Video avatar looks the oposite way, so this just blows the particles to the opposite direction */ async emoji(client, direction=3) { super.emoji(client, -direction); } /** * Handles name change network event */ setName(name) { super.setName(name); if ( name ) { this.altText = name; } else { this.altText = "N/A"; } } }