UNPKG

@vrspace/babylonjs

Version:

vrspace.org babylonjs client

231 lines (216 loc) 10.6 kB
import { VRSPACEUI } from "../vrspace-ui.js"; import { World } from "../../world/world.js"; /** * Plane manipulation UI: adds handles around a plane, and installs pointer drag observable to the scene. * Top and bottom handles are used to move the plane around, with 6DOF. * Left and right handles resize the plane. * If this.canMinimize is set, also adds a top-right box that disables plane's parent and all of it's children, * except ones specified in dontMinimize. * Material and parent are taken from the plane. * While initially intended to be used for manipulation of a plane, this can be used to manipulate any mesh. */ export class ManipulationHandles { /** * Create the control. * @param plane mesh to manipulate * @param width mesh width, determines how wide are handles * @param height mesh height, how far up and down are handles * @param scene babylon scene */ constructor(plane, width, height, scene) { this.plane = plane; this.material = plane.material; this.width = width; this.height = height; this.group = plane.parent; this.scene = scene; this.segments = 8; this.canMinimize = true; this.canClose = false; this.onClose = null; this.minimized = false; this.dontMinimize = []; this.sizeCallback = null; this.positionCallback = null; this.selectedMaterial = new BABYLON.StandardMaterial("selectedMaterial", this.scene); this.selectedMaterial.alpha = this.material.alpha; this.selectedMaterial.diffuseColor = new BABYLON.Color3(.2, .5, .2); this.alertMaterial = new BABYLON.StandardMaterial("alertMaterial", this.scene); this.alertMaterial.alpha = this.material.alpha; this.alertMaterial.diffuseColor = new BABYLON.Color3(.3, 0, 0); /** minimization button TODO rename */ this.box = null; this.closeButton = null; /** Callback on area minimized/maximized, passed a minimized/hidden flag */ this.onMinMax = null; } /** * Creates manipulation handles. * Left and right handle resize, and top and bottom move it, optional box disables/reenables everything. */ show() { let handleWidth = this.height / 25; this.leftHandle = BABYLON.MeshBuilder.CreateSphere("leftHandle", { segments: this.segments }, this.scene); this.leftHandle.scaling = new BABYLON.Vector3(handleWidth, this.height, handleWidth); this.leftHandle.position = new BABYLON.Vector3(-this.width / 2 - this.width / 20, 0, 0); this.leftHandle.parent = this.group; this.leftHandle.material = this.material; this.rightHandle = BABYLON.MeshBuilder.CreateSphere("rightHandle", { segments: this.segments }, this.scene); this.rightHandle.scaling = new BABYLON.Vector3(handleWidth, this.height, handleWidth); this.rightHandle.position = new BABYLON.Vector3(this.width / 2 + this.width / 20, 0, 0); this.rightHandle.parent = this.group; this.rightHandle.material = this.material; this.topHandle = BABYLON.MeshBuilder.CreateSphere("topHandle", { segments: this.segments }, this.scene); this.topHandle.scaling = new BABYLON.Vector3(this.width, handleWidth, handleWidth); this.topHandle.position = new BABYLON.Vector3(0, this.height / 2 + this.height / 20, 0); this.topHandle.parent = this.group; this.topHandle.material = this.material; this.bottomHandle = BABYLON.MeshBuilder.CreateSphere("bottomHandle", { segments: this.segments }, this.scene); this.bottomHandle.scaling = new BABYLON.Vector3(this.width, handleWidth, handleWidth); this.bottomHandle.position = new BABYLON.Vector3(0, -this.height / 2 - this.height / 20, 0); this.bottomHandle.parent = this.group; this.bottomHandle.material = this.material; if (this.canMinimize) { // CHECKME 1 may be too small on mobiles this.box = BABYLON.MeshBuilder.CreatePlane("MinMaxButon", { width: 1, height: 1 }, this.scene); this.box.scaling = new BABYLON.Vector3(handleWidth, handleWidth, this.height / 100); this.box.position = new BABYLON.Vector3(-this.width / 2 - this.width / 20, -this.height / 2 - this.height / 20, 0); this.box.parent = this.group; this.box.material = this.material.clone(); this.box.material.diffuseTexture = new BABYLON.Texture(VRSPACEUI.contentBase + "/content/icons/minimize.png", this.scene); this.box.material.diffuseTexture.hasAlpha = true; this.box.material.emissiveColor = BABYLON.Color3.White(); } if (this.canClose) { this.closeButton = BABYLON.MeshBuilder.CreatePlane("MinMaxButon", { width: 1, height: 1 }, this.scene); this.closeButton.scaling = new BABYLON.Vector3(handleWidth, handleWidth, this.height / 100); this.closeButton.position = new BABYLON.Vector3(this.width / 2 + this.width / 20, -this.height / 2 - this.height / 20, 0); this.closeButton.parent = this.group; this.closeButton.material = this.material.clone(); this.closeButton.material.diffuseTexture = new BABYLON.Texture(VRSPACEUI.contentBase + "/content/icons/close.png", this.scene); this.closeButton.material.diffuseTexture.hasAlpha = true; this.closeButton.material.emissiveColor = BABYLON.Color3.White(); } this.bottomHandle.opposite = this.topHandle; this.topHandle.opposite = this.bottomHandle; this.leftHandle.opposite = this.rightHandle; this.rightHandle.opposite = this.leftHandle; this.handles = [this.leftHandle, this.topHandle, this.rightHandle, this.bottomHandle]; this.resizeHandler = this.scene.onPointerObservable.add((pointerInfo) => { if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) { //if ( pointerInfo.pickInfo.hit && this.handles.includes(pointerInfo.pickInfo.pickedMesh) ) { if (pointerInfo.pickInfo.hit) { if (pointerInfo.pickInfo.pickedMesh == this.bottomHandle || pointerInfo.pickInfo.pickedMesh == this.topHandle) { // moving around if (!this.behavior) { this.behavior = this.createBehavior(); // does not work if group.parent is camera this.group.addBehavior(this.behavior); pointerInfo.pickInfo.pickedMesh.material = this.selectedMaterial; this.selectedHandle = pointerInfo.pickInfo.pickedMesh; } } else if (pointerInfo.pickInfo.pickedMesh == this.leftHandle || pointerInfo.pickInfo.pickedMesh == this.rightHandle) { // scaling if (!this.selectedHandle) { this.selectedHandle = pointerInfo.pickInfo.pickedMesh; this.point = pointerInfo.pickInfo.pickedPoint; pointerInfo.pickInfo.pickedMesh.material = this.selectedMaterial; } } else if (pointerInfo.pickInfo.pickedMesh == this.box) { // minimizing/maximizing (hiding/showing) this.hide(!this.minimized); } else if (pointerInfo.pickInfo.pickedMesh == this.closeButton) { if (this.onClose) { this.onClose(); } } } else if (this.selectedHandle) { this.selectedHandle.material = this.material; this.selectedHandle = null; if (this.behavior) { this.group.removeBehavior(this.behavior); this.behavior = null; } } } if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP && this.selectedHandle) { if (pointerInfo.pickInfo.hit && (pointerInfo.pickInfo.pickedMesh == this.leftHandle || pointerInfo.pickInfo.pickedMesh == this.rightHandle)) { let diff = pointerInfo.pickInfo.pickedPoint.y - this.point.y; let scale = (this.height + diff) / this.height; this.group.scaling = this.group.scaling.scale(scale); if (this.sizeCallback) { this.sizeCallback(this.group.scaling); } } if (this.selectedHandle) { this.selectedHandle.material = this.material; this.selectedHandle = null; if (this.behavior) { this.group.removeBehavior(this.behavior); this.behavior = null; if (this.positionCallback) { if ( this.group.rotationQuaternion ) { this.positionCallback(this.group.position, this.group.rotationQuaternion.toEulerAngles()); } else { this.positionCallback(this.group.position, this.group.rotation); } //this.positionCallback(this.group.position, this.group.rotationQuaternion); } } } } }); } createBehavior() { if (World.lastInstance.inXR()) { return new BABYLON.SixDofDragBehavior(); } //if ( this.group.billboardMode == BABYLON.Mesh.BILLBOARDMODE_Y ) { return new BABYLON.PointerDragBehavior({ dragPlaneNormal: new BABYLON.Vector3(0, 0, 1) }); //return new BABYLON.PointerDragBehavior({ dragAxis: new BABYLON.Vector3(0, 1, 0) }); } /** * Minimize or maximize (hide or show all children of this.group) * @param flag boolean indicating whether to hide or show children */ hide(flag) { if (this.canMinimize) { //console.log("Hiding handles: "+flag); this.group.getChildMeshes().forEach(h => { if (h !== this.box && !this.dontMinimize.includes(h)) { h.setEnabled(!flag); } }); this.minimized = flag; if (this.minimized) { this.box.material.diffuseTexture = new BABYLON.Texture(VRSPACEUI.contentBase + "/content/icons/maximize.png", this.scene); } else { this.box.material.diffuseTexture = new BABYLON.Texture(VRSPACEUI.contentBase + "/content/icons/minimize.png", this.scene); } if (this.onMinMax) { this.onMinMax(this.minimized); } } } /** * XR pointer support */ isSelectableMesh(mesh) { return mesh.isEnabled() && (!this.handles.minimized && this.handles.includes(mesh) || this.box && mesh == this.box || this.closeButton && mesh == this.closeButton); } /** * Clean up */ dispose() { this.scene.onPointerObservable.remove(this.resizeHandler); this.handles.forEach(h => h.dispose()); if (this.box) { this.box.dispose(); } if (this.closeButton) { this.closeButton.dispose(); } this.selectedMaterial.dispose(); this.alertMaterial.dispose(); } }