UNPKG

@vrspace/babylonjs

Version:

vrspace.org babylonjs client

358 lines (348 loc) 13.2 kB
import {VRSPACEUI} from '../vrspace-ui.js'; import { CameraHelper } from '../../core/camera-helper.js'; /** UI to create floors, see {@link https://www.youtube.com/watch?v=8RxToSgtoko|this youtube video}. Start recording, then edit, then save, either as js or json. UI Buttons are bound to current camera. */ export class FloorRibbon { /** @param world a World to show in @param size floor size, default 1 m */ constructor( world, size ) { // parameters this.world = world; this.scene = world.scene; if ( size ) { this.size = size; } else { this.size = 1; } this.decimals = 2; this.floorMaterial = new BABYLON.StandardMaterial("floorMaterial", this.scene); this.floorMaterial.diffuseColor = new BABYLON.Color3(.5, 1, .5); this.floorMaterial.backFaceCulling = false; this.floorMaterial.alpha = 0.5; // state variables this.leftPath = []; this.rightPath = []; this.pathArray = [this.leftPath, this.rightPath]; this.left = BABYLON.MeshBuilder.CreateSphere("leftSphere", {diameter: 1}, this.scene); this.right = BABYLON.MeshBuilder.CreateSphere("rightSphere", {diameter: 1}, this.scene); this.left.isVisible = false; this.right.isVisible = false; CameraHelper.getInstance(this.scene).addCameraListener(()=>this.cameraChanged()); this.recording = false; this.editing = false; this.resizing = false; this.floorCount = 0; this.contentBase=VRSPACEUI.contentBase; // required for Scene.pointerMovePredicate, resizing the ribbon this.world.addSelectionPredicate(mesh=>this.isSelectableMesh(mesh)); } cameraChanged() { console.log("Camera changed: "+this.scene.activeCamera.getClassName()+" new position "+this.scene.activeCamera.position); this.camera = this.scene.activeCamera; this.left.parent = this.camera; this.right.parent = this.camera; } /** Shows the UI */ showUI() { this.recordButton = VRSPACEUI.hud.addButton("Start", this.contentBase+"/content/icons/play.png"); this.recordButton.onPointerDownObservable.add( () => this.startStopCancel()); this.editButton = VRSPACEUI.hud.addButton("Edit",this.contentBase+"/content/icons/edit.png"); this.editButton.onPointerDownObservable.add( () => this.edit()); this.jsonButton = VRSPACEUI.hud.addButton("JSON", this.contentBase+"/content/icons/download.png"); this.jsonButton.onPointerDownObservable.add( () => this.saveJson()); this.jsButton = VRSPACEUI.hud.addButton("JS", this.contentBase+"/content/icons/download.png"); this.jsButton.onPointerDownObservable.add( () => this.saveJs()); this.editButton.isVisible = false; this.jsonButton.isVisible = false; this.jsButton.isVisible = false; } startStopCancel() { if ( this.floorMesh ) { // cancel this.floorMesh.dispose(); delete this.floorMesh; this.leftPath = []; this.rightPath = []; this.pathArray = [ this.leftPath, this.rightPath ]; this.recordButton.text="Start"; } else { this.recording = !this.recording; if ( this.recording ) { // start this.startRecording(); this.recordButton.text="Pause"; } else { // stop this.createPath(); this.recordButton.text="Cancel"; } } this.updateUI(); } updateUI() { if ( this.recording ) { this.recordButton.imageUrl = this.contentBase+"/content/icons/pause.png"; } else if ( this.floorMesh) { this.recordButton.imageUrl = this.contentBase+"/content/icons/undo.png"; } else { this.recordButton.imageUrl = this.contentBase+"/content/icons/play.png"; } this.editButton.isVisible = !this.recording && this.floorMesh; this.jsonButton.isVisible = !this.recording && this.floorMesh; this.jsButton.isVisible = !this.recording && this.floorMesh; } trackActiveCamera() { var camera = this.scene.activeCamera; if ( camera ) { this.trackCamera(camera); } } startRecording() { this.leftPath = []; this.rightPath = []; this.pathArray = [ this.leftPath, this.rightPath ]; this.trackActiveCamera(); } trackCamera(camera) { console.log("Tracking camera"); if ( camera ) { this.camera = camera; } this.lastX = this.camera.position.x; this.lastZ = this.camera.position.z; this.observer = this.camera.onViewMatrixChangedObservable.add((c) => this.viewChanged(c)); this.left.parent = camera; this.right.parent = camera; var height = camera.ellipsoid.y*2; if ( this.camera.getClassName() == 'WebXRCamera' ) { var height = this.camera.realWorldHeight; } this.left.position = new BABYLON.Vector3(-1, -height, 0); this.right.position = new BABYLON.Vector3(1, -height, 0); } viewChanged(camera) { if ( camera.position.x > this.lastX + this.size || camera.position.x < this.lastX - this.size || camera.position.z > this.lastZ + this.size || camera.position.z < this.lastZ - this.size ) { //console.log("Pos: "+camera.position); //console.log("Pos left: "+this.left.absolutePosition+" right: "+this.right.absolutePosition); this.lastX = camera.position.x; this.lastZ = camera.position.z; if ( this.recording ) { this.leftPath.push( this.left.absolutePosition.clone() ); this.rightPath.push( this.right.absolutePosition.clone() ); } } } createPath() { if ( this.leftPath.length > 1 ) { this.addToScene(); } this.camera.onViewMatrixChangedObservable.remove(this.observer); delete this.observer; } addToScene() { //var floorGroup = new BABYLON.TransformNode("floorGroup"); //this.scene.addTransformNode( floorGroup ); this.floorCount++; var floorMesh = BABYLON.MeshBuilder.CreateRibbon( "FloorRibbon"+this.floorCount, {pathArray: this.pathArray, updatable: true}, this.scene ); floorMesh.material = this.floorMaterial; floorMesh.checkCollisions = false; this.floorMesh = floorMesh; } isSelectableMesh(mesh) { return mesh && this.floorMesh && this.floorMesh == mesh; } clear(){ delete this.floorMesh; this.leftPath = []; this.rightPath = []; this.pathArray = [ this.leftPath, this.rightPath ]; this.updateUI(); } edit() { if ( ! this.floorMesh ) { return; } this.recordButton.isVisible = this.editing; this.jsonButton.isVisible = this.editing; this.jsButton.isVisible = this.editing; this.editing = !this.editing; if ( this.resizing ) { this.scene.onPointerObservable.remove( this.observer ); this.resizing = false; delete this.observer; delete this.pathPoints; delete this.point1; delete this.point2; this.editButton.imageUrl = this.contentBase+"/content/icons/edit.png"; if ( this.edgeMesh ) { this.edgeMesh.dispose(); delete this.edgeMesh; } } else if ( this.editing ) { this.editButton.imageUrl = this.contentBase+"/content/icons/back.png"; this.editButton.text = "Pick 1"; this.resizing = true; this.observer = this.scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: if(pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh == this.floorMesh) { if ( ! this.point1 ) { this.point1 = this.pickClosest(pointerInfo.pickInfo); this.editButton.text = "Pick 2"; } else if ( ! this.point2 ) { this.point2 = this.pickClosest(pointerInfo.pickInfo); this.selectEdge(); this.editButton.text = "Drag"; } else { this.pickedPoint = this.pickClosest(pointerInfo.pickInfo); this.editButton.imageUrl = "/content/icons/tick.png"; this.editButton.text = null; } } break; case BABYLON.PointerEventTypes.POINTERUP: delete this.pickedPoint; break; case BABYLON.PointerEventTypes.POINTERMOVE: if ( this.pickedPoint && pointerInfo.pickInfo.pickedMesh == this.floorMesh ) { this.resizeRibbon( pointerInfo.pickInfo.pickedPoint ); } break; } }); } else if ( this.observer ) { this.editButton.text = null; this.scene.onPointerObservable.remove( this.observer ); } } pickClosest( pickInfo ) { var pickedIndex = 0; var pickedLeft = false; var path; var pathPoint; var min = 100000; for ( var i = 0; i < this.leftPath.length; i++ ) { var leftDistance = pickInfo.pickedPoint.subtract( this.leftPath[i] ).length(); var rightDistance = pickInfo.pickedPoint.subtract( this.rightPath[i] ).length(); if ( leftDistance < min ) { min = leftDistance; pickedLeft = true; pickedIndex = i; path = this.leftPath; pathPoint = this.leftPath[i]; } if ( rightDistance < min ) { min = rightDistance; pickedLeft = false; pickedIndex = i; path = this.rightPath; pathPoint = this.rightPath[i]; } } var ret = { index: pickedIndex, path: path, left: pickedLeft, pathPoint: pathPoint, point: pickInfo.pickedPoint.clone() }; console.log("Picked left: "+pickedLeft+" index: "+pickedIndex+"/"+path.length+" distance: "+min); return ret; } selectEdge() { if ( this.point1.index > this.point2.index ) { var tmp = this.point2; this.point2 = this.point1; this.point1 = tmp; } var points = [] for ( var i = this.point1.index; i <= this.point2.index; i++ ) { if ( this.point1.left ) { points.push( this.leftPath[i] ); } else { points.push( this.rightPath[i] ); } } this.pathPoints = points; if ( this.pathPoints.length > 1 ) { this.edgeMesh = BABYLON.MeshBuilder.CreateLines("FloorEdge", {points: points, updatable: true}, this.scene ); } else { this.edgeMesh = BABYLON.MeshBuilder.CreateSphere("FloorEdge", {diameter:0.1}, this.scene); this.edgeMesh.position = this.pathPoints[0]; } } resizeRibbon(point) { var diff = point.subtract(this.pickedPoint.point); for (var i = 0; i < this.pathPoints.length; i++ ) { this.pathPoints[i].addInPlace(diff); } this.pickedPoint.point = point.clone(); // update the ribbon // seems buggy: //BABYLON.MeshBuilder.CreateRibbon( "FloorRibbon"+this.floorCount, {pathArray: this.pathArray, instance: this.floorMesh}); var floorMesh = BABYLON.MeshBuilder.CreateRibbon( "FloorRibbon"+this.floorCount, {pathArray: this.pathArray, updatable: true}, this.scene ); floorMesh.material = this.floorMaterial; floorMesh.checkCollisions = false; this.floorMesh.dispose(); this.floorMesh = floorMesh; // update the edge if ( this.pathPoints.length > 1 ) { BABYLON.MeshBuilder.CreateLines("FloorEdge", {points: this.pathPoints, instance: this.edgeMesh} ); } } saveJson() { var json = this.printJson(); VRSPACEUI.saveFile('FloorRibbon'+this.floorCount+'.json', json); this.clear(); } saveJs() { var js = this.printJs(); VRSPACEUI.saveFile('FloorRibbon'+this.floorCount+'.js', js); this.clear(); } printJson() { var ret = '{"pathArray":\n'; ret += "[\n"; ret += this.printPathJson(this.leftPath); ret += "\n],[\n"; ret += this.printPathJson(this.rightPath); ret += "\n]}"; console.log(ret); return ret; } printJs() { var ret = "BABYLON.MeshBuilder.CreateRibbon( 'FloorRibbon"+this.floorCount+"', {pathArray: \n"; ret += "[[\n"; ret += this.printPathJs(this.leftPath); ret += "\n],[\n"; ret += this.printPathJs(this.rightPath); ret += "\n]]}, scene );"; console.log(ret); return ret; } printPathJs(path) { var ret = ""; for ( var i = 0; i < path.length-1; i++ ) { ret += "new BABYLON.Vector3("+path[i].x.toFixed(this.decimals)+","+path[i].y.toFixed(this.decimals)+","+path[i].z.toFixed(this.decimals)+"),"; } ret += "new BABYLON.Vector3("+path[path.length-1].x.toFixed(this.decimals)+","+path[path.length-1].y.toFixed(this.decimals)+","+path[path.length-1].z.toFixed(this.decimals)+")"; return ret; } printPathJson(path) { var ret = ""; for ( var i = 0; i < path.length-1; i++ ) { ret += "["+path[i].x.toFixed(this.decimals)+","+path[i].y.toFixed(this.decimals)+","+path[i].z.toFixed(this.decimals)+"],"; } ret += "["+path[path.length-1].x.toFixed(this.decimals)+","+path[path.length-1].y.toFixed(this.decimals)+","+path[path.length-1].z.toFixed(this.decimals)+"]"; return ret; } }