UNPKG

@vrspace/babylonjs

Version:

vrspace.org babylonjs client

292 lines (267 loc) 9.73 kB
import { ManipulationHandles } from "./manipulation-handles.js"; import { BaseArea } from './base-area.js'; import { VRSPACEUI } from "../vrspace-ui.js"; /** * An area somewhere in space, like a screen, displaying a texture. */ export class ImageArea extends BaseArea { /** * Creates the area with default values. * By default, it's sized and positioned to be attached to the camera, and includes manipulation handles */ constructor(scene, name="ImageArea-root") { super(scene, name); this.position = new BABYLON.Vector3(0, 0, .3); this.width = 2048; this.height = 1024; this.autoResize = true; this.visible = false; this.noiseTexture = null; this.callback = null; // CHECKME - onClick only used in test to start the video this.pointerIsDown = false; } /** Show the area, optionally also creates manipulation handles */ show () { if ( this.visible ) { return; } this.visible = true; this.group.billboardMode = this.billboardMode; this.group.position = this.position; this.ratio = this.width/this.height; this.material = new BABYLON.StandardMaterial("ImageMaterial", this.scene); this.material.emissiveColor = new BABYLON.Color3(0,1,0); this.material.disableLighting = true; this.material.backFaceCulling = false; this.noiseTexture = new BABYLON.NoiseProceduralTexture(this.name+"-perlin", 256, this.scene); this.material.diffuseTexture = this.noiseTexture; this.noiseTexture.octaves = 10; this.noiseTexture.persistence = 1.5; this.noiseTexture.animationSpeedFactor = 3; this.areaPlane = BABYLON.MeshBuilder.CreatePlane("ImageAreaPlane", {width:1,height:1}, this.scene); this.areaPlane.scaling.x = this.size*this.ratio; this.areaPlane.scaling.y = this.size; this.areaPlane.parent = this.group; this.areaPlane.material = this.material; this.areaPlane.visibility = 0.1; this.areaPlane.enablePointerMoveEvents = true; //this.areaPlane.pointerOverDisableMeshTesting = true; // no effect if (this.addHandles) { this.createHandles(); } this.clickHandler = this.scene.onPointerObservable.add((pointerInfo) => { //console.log(pointerInfo.type+" "+pointerInfo.pickInfo.hit+" "+(this.areaPlane == pointerInfo.pickInfo.pickedMesh)); if ( pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN && pointerInfo.pickInfo.hit && this.areaPlane == pointerInfo.pickInfo.pickedMesh ) { //console.log("Clicked: x="+x+" y="+y+" coord "+pointerInfo.pickInfo.getTextureCoordinates() ); let coords = pointerInfo.pickInfo.getTextureCoordinates(); let y = Math.round(this.height*(1-coords.y)); let x = Math.round(coords.x*this.width); this.click(x,y); this.pointerIsDown = true; } else if ( pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP && this.pointerIsDown ) { this.pointerIsDown = false; this.pointerUp(); } else if ( this.pointerIsDown && pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE && pointerInfo.pickInfo.hit && this.areaPlane == pointerInfo.pickInfo.pickedMesh ) { let coords = pointerInfo.pickInfo.getTextureCoordinates(); let y = Math.round(this.height*(1-coords.y)); let x = Math.round(coords.x*this.width); this.pointerDrag(x,y); } }); } /** * Internally used while replacing the texture * @private */ texturesDispose() { if ( this.noiseTexture ) { this.noiseTexture.dispose(); this.noiseTexture = null; } if ( this.texture ) { this.texture.dispose(); this.texture = null; } } /** * Internally used after texture is set, sets emissiveColor and visibility */ fullyVisible() { this.material.emissiveColor = new BABYLON.Color3(1,1,1); this.areaPlane.visibility = 1; } /** * Load the texture from the url */ loadUrl(url) { let texture = new BABYLON.Texture(url, this.scene); this.texturesDispose(); this.material.diffuseTexture = texture; this.texture = texture; this.fullyVisible(); texture.onLoadObservable.add(() => this.resizeArea(texture.getSize().width, texture.getSize().height)); } /** * Load texture from the data buffer, e.g. blob */ loadData(data, name="bufferedTexture") { console.log("Loading texture, size "+data.size); this.texturesDispose(); let texture = BABYLON.Texture.LoadFromDataString(name,data,this.scene); this.material.diffuseTexture = texture; this.texture = texture; this.fullyVisible(); this.resizeArea(texture.getSize().width, texture.getSize().height); } /** Load video texture from the url, and by default also creates and plays the spatial sound. */ loadVideo(url, playSound=true) { let texture = new BABYLON.VideoTexture(null, url, this.scene); this.texturesDispose(); this.material.diffuseTexture = texture; this.texture = texture; this.fullyVisible(); console.log("Loaded video "+url+" playing sound: "+playSound); if ( playSound ) { this.sound = new BABYLON.Sound( "videoTextureSound", texture.video, this.scene, null, { //loop: true, autoplay: true, spatialSound: true, //streaming: false, distanceModel: "linear", maxDistance: 10, panningModel: "equalpower" // or "HRTF" }); this.sound.attachToMesh(this.areaPlane); this.attachVolumeControl(); } console.log(texture.video.videoWidth+"x"+texture.video.videoHeight); // resize the plane texture.video.onresize = () => { this.resizeArea(texture.video.videoWidth,texture.video.videoHeight); } } /** * Load a MediaStream, and resize the plane */ loadStream(mediaStream) { console.log("Attaching media stream", mediaStream); BABYLON.VideoTexture.CreateFromStreamAsync(this.scene, mediaStream).then( (texture) => { this.texturesDispose(); this.texture = texture; this.material.diffuseTexture = texture; this.material.diffuseTexture.vScale = -1 this.fullyVisible(); }); let mediaTrackSettings = mediaStream.getVideoTracks()[0].getSettings(); //console.log('Playing video track', mediaTrackSettings); /* // local: frameRate: 30 height: 2160 width: 3840 // remote: aspectRatio: 1.7774436090225565 deviceId: "96a38882-6269-454e-8009-3d3960b343ba" frameRate: 30 height: 1330 resizeMode: "none" width: 2364 */ // now resize the area this.resizeArea(mediaTrackSettings.width, mediaTrackSettings.height); } /** Internally used to resize the plane once video/image resolution is known */ resizeArea(width, height) { if ( this.autoResize && width && height ) { //console.log("ImageArea resizing to "+width+"x"+height); this.width = width; this.height = height; this.ratio = this.width/this.height; this.areaPlane.scaling.x = this.size*this.ratio; this.areaPlane.scaling.y = this.size; if ( this.addHandles ) { // FIXME introduce handles.resize() to preserve all state variables let positionCallback = this.handles.positionCallback; let sizeCallback = this.handles.sizeCallback if ( this.handles ) { this.handles.dispose(); } this.createHandles(); this.handles.positionCallback = positionCallback; this.handles.sizeCallback = sizeCallback; } } } /** * Creates manipulation handles. Left and right handle resize, and top and bottom move it. */ createHandles() { this.handles = new ManipulationHandles(this.areaPlane, this.size*this.ratio, this.size, this.scene); this.handles.material = VRSPACEUI.uiMaterial; /* this.handles.material = new BABYLON.StandardMaterial("TextAreaMaterial", this.scene); this.handles.material.alpha = 0.75; this.handles.material.diffuseColor = new BABYLON.Color3(.2,.2,.3); */ this.handles.canMinimize = this.canMinimize; this.handles.canClose = this.canClose; this.handles.onClose = this.onClose; this.handles.show(); this.attachVolumeControl(); } attachVolumeControl() { if ( this.handles && this.sound && !this.handles.onMinMax ) { this.handles.onMinMax = minimized => { console.log("Minimized: "+minimized); if ( minimized ) { this.sound.setVolume(0,1); } else { this.sound.setVolume(1,1); } } } } /** Called on pointer event, passed texture coordinates. Executes callback */ async click(x,y) { if ( this.callback ) { this.callback(this,x,y); } } /** Called on pointer event */ pointerUp() { } /** Called on pointer event, passed texture coordinates */ pointerDrag(x, y) { } /** * Set click event handler here * @param callback executed on pointer click, passed Control argument */ onClick(callback) { this.callback = callback; } /** Clean up. */ dispose() { super.dispose(); if ( this.material ) { this.material.dispose(); } if ( this.clickHandler) { this.scene.onPointerObservable.remove(this.clickHandler); } if ( this.sound ) { this.sound.dispose(); } this.texturesDispose(); } }