import { AbstractMesh, Color3, GizmoManager, InstancedMesh, Mesh, PointerEventTypes, PointerInfo, Scene, StandardMaterial, Vector3, WebXRDefaultExperience, } from "@babylonjs/core"; import {GUI3DManager, PlanePanel} from "@babylonjs/gui"; import {DiagramManager} from "../diagram/diagramManager"; import {EditMenuState} from "./editMenuState"; import {DiagramEvent, DiagramEventType} from "../diagram/types/diagramEntity"; import log from "loglevel"; import {InputTextView} from "../information/inputTextView"; import {DiaSounds} from "../util/diaSounds"; import {updateTextNode} from "../util/functions/updateTextNode"; import {DiagramConnection} from "../diagram/diagramConnection"; import {toDiagramEntity} from "../diagram/functions/toDiagramEntity"; import {AbstractMenu} from "./abstractMenu"; import {Controllers} from "../controllers/controllers"; import {setMenuPosition} from "../util/functions/setMenuPosition"; import {SoccerMenu} from "../soccer/soccerMenu"; import {CameraMenu} from "./cameraMenu"; import {exportGltf} from "../util/functions/exportGltf"; import {isDiagramEntity} from "../diagram/functions/isDiagramEntity"; import {ScaleMenu} from "./scaleMenu"; export class EditMenu extends AbstractMenu { private state: EditMenuState = EditMenuState.NONE; private manager: GUI3DManager; private paintColor: string = null; private readonly logger: log.Logger = log.getLogger('EditMenu'); private gizmoManager: GizmoManager; private readonly diagramManager: DiagramManager; private connection: DiagramConnection = null; private panel: PlanePanel; private cameraMenu: CameraMenu = null; private sounds: DiaSounds; private get isVisible(): boolean { return this.panel.isVisible; } private soccerMenu: SoccerMenu; private set isVisible(visible: boolean) { this.panel.isVisible = visible; this.panel.children.forEach((child) => { child.isVisible = visible; }); this.handle.mesh.isVisible = visible; } private getTool(template: string, color: Color3): Mesh { const baseMeshId = 'tool-' + template + '-' + color.toHexString(); return (this.scene.getMeshById(baseMeshId) as Mesh); } private persist(mesh: AbstractMesh, text: string) { if (mesh.metadata) { mesh.metadata.text = text; } else { this.logger.error("mesh has no metadata"); } this.diagramManager.onDiagramEventObservable.notifyObservers({ type: DiagramEventType.MODIFY, entity: toDiagramEntity(mesh), }, -1); } toggle() { if (this.isVisible) { this.sounds.exit.play(); this.isVisible = false; } else { this.sounds.enter.play(); setMenuPosition(this.handle.mesh, this.scene, new Vector3(-.6, .4, 0)); this.isVisible = true; } } makeButton(name: string, id: string) { const button = super.makeButton(name, id); button.onPointerClickObservable.add(this.handleClick, -1, false, this); return button; } private async diagramEntityPicked(pointerInfo: PointerInfo) { const mesh = pointerInfo.pickInfo.pickedMesh; if (!mesh) { this.logger.warn("no mesh"); return; } switch (this.state) { case EditMenuState.REMOVING: this.removeMesh(mesh); break; case EditMenuState.MODIFYING: this.modifyMesh(mesh); break; case EditMenuState.LABELING: this.labelMesh(mesh); break; case EditMenuState.COPYING: this.copyMesh(mesh); break; case EditMenuState.CONNECTING: this.createMeshConnection(mesh, pointerInfo); break; case EditMenuState.RECOLORING: if (this.paintColor) { const template = mesh.metadata.template; const newBase = this.getTool(template, Color3.FromHexString(this.paintColor)); if (!newBase) { return; } const newMesh = (mesh as InstancedMesh).clone(mesh.name, mesh.parent, false, newBase); newMesh.id = mesh.id; newMesh.physicsBody = mesh.physicsBody; newMesh.metadata = mesh.metadata; mesh.physicsBody = null; mesh.dispose(); this.diagramManager.onDiagramEventObservable.notifyObservers({ type: DiagramEventType.MODIFY, entity: toDiagramEntity(newMesh) }, -1); } else { this.logger.error("no paint color selectced"); } } } private createMeshConnection(mesh: AbstractMesh, pointerInfo) { if (this.connection) { this.connection.to = mesh.id; this.diagramManager.onDiagramEventObservable.notifyObservers({ type: DiagramEventType.ADD, entity: toDiagramEntity(this.connection.mesh) }, -1); this.connection = null; } else { this.connection = new DiagramConnection(mesh.id, null, null, this.scene, pointerInfo); } } private removeMesh(mesh: AbstractMesh) { this.logger.debug("removing " + mesh?.id); const event: DiagramEvent = { type: DiagramEventType.REMOVE, entity: toDiagramEntity(mesh) } this.diagramManager.onDiagramEventObservable.notifyObservers(event, -1); } private scaleMenu: ScaleMenu; constructor(scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager, controllers: Controllers) { super(scene, xr, controllers); //this.scaleMenu = new ScaleMenu(this.scene, this.xr, this.controllers); this.sounds = new DiaSounds(scene); this.diagramManager = diagramManager; /*this.gizmoManager = new GizmoManager(scene); this.gizmoManager.boundingBoxGizmoEnabled = true; this.gizmoManager.gizmos.boundingBoxGizmo.scaleBoxSize = .020; this.gizmoManager.gizmos.boundingBoxGizmo.rotationSphereSize = .020; this.gizmoManager.gizmos.boundingBoxGizmo.scaleDragSpeed = 2; this.gizmoManager.clearGizmoOnEmptyPointerEvent = true; this.gizmoManager.usePointerToAttachGizmos = false;*/ this.manager = new GUI3DManager(this.scene); const panel = new PlanePanel(); panel.orientation = PlanePanel.FACEFORWARDREVERSED_ORIENTATION; panel.columns = 4; this.manager.addControl(panel); //panel.addControl(this.makeButton("Cameras", "camera")); //panel.addControl(this.makeButton("Modify", "modify")); panel.addControl(this.makeButton("Remove", "remove")); panel.addControl(this.makeButton("Label", "label")); panel.addControl(this.makeButton("Copy", "copy")); panel.addControl(this.makeButton("Connect", "connect")); //panel.addControl(this.makeButton("Export GLTF", "exportgltf")); //panel.addControl(this.makeButton("Recolor", "recolor")); //panel.addControl(this.makeButton("New Relic", "newrelic")); //panel.addControl(this.makeButton("Soccer", "soccer")); //panel.addControl(this.makeButton("Add Ring Cameras", "addRingCameras")); this.manager.controlScaling = .1; this.scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case PointerEventTypes.POINTERPICK: const pickedMesh = pointerInfo.pickInfo?.pickedMesh; if (isDiagramEntity(pickedMesh) && pickedMesh?.parent?.parent?.id != "toolbox") { this.diagramEntityPicked(pointerInfo).then(() => { this.logger.debug("handled"); }).catch((e) => { this.logger.error(e); }); break; } else { const tool = pickedMesh?.metadata?.tool; if (tool) { this.logger.debug("tool type", tool); this.paintColor = (pickedMesh.material as StandardMaterial).diffuseColor.toHexString(); this.logger.debug((pickedMesh.material as StandardMaterial).diffuseColor.toHexString()); this.logger.debug(pickedMesh.id); } } } }); this.panel = panel; this.createHandle(this.manager.rootContainer.children[0].node); this.manager.rootContainer.children[0].node.position.y = .15; this.isVisible = false; } private copyMesh(mesh: AbstractMesh) { if (mesh) { const newMesh = this.diagramManager.createCopy(mesh, true); newMesh.setParent(mesh.parent); } this.logger.warn('copying not implemented', mesh); //@todo implement } private labelMesh(mesh: AbstractMesh) { this.logger.debug("labeling " + mesh.id); let text = ""; if (mesh?.metadata?.text) { text = mesh.metadata.text; } const textInput = new InputTextView(text, this.xr, this.scene, this.controllers); textInput.show(); textInput.onTextObservable.addOnce((value) => { this.persist(mesh, value.text); updateTextNode(mesh, value.text); }); } private showNewRelic() { const inputTextView = new InputTextView('test', this.xr, this.scene, this.controllers); inputTextView.show(); inputTextView.onTextObservable.addOnce((value) => { const config = this.diagramManager.config.current; config.newRelicKey = value.text; this.diagramManager.config.current = config; inputTextView.show(); inputTextView.onTextObservable.addOnce((value) => { const config = this.diagramManager.config.current; config.newRelicAccount = value.text; this.diagramManager.config.current = config; }); }); } private modifyMesh(mesh: AbstractMesh) { if (isDiagramEntity(mesh) && mesh.parent?.parent?.id != "toolbox") { this.scaleMenu.changeMesh(mesh); } } private handleClick(_info, state) { switch (state.currentTarget.name) { case "modify": this.state = EditMenuState.MODIFYING; break; case "remove": this.state = EditMenuState.REMOVING; break; case "label": this.state = EditMenuState.LABELING; break; case "copy": this.state = EditMenuState.COPYING; break; case "connect": this.state = EditMenuState.CONNECTING; break; case "recolor": this.state = EditMenuState.RECOLORING; break; case "newrelic": this.showNewRelic(); break; case "exportgltf": this.downloadgltf(); break; case "soccer": this.createSoccerField(); break; case "camera": if (!this.cameraMenu) { this.cameraMenu = new CameraMenu(this.scene, null, null); } break; default: this.logger.error("Unknown button"); return; } this.isVisible = false; } private createSoccerField() { if (!this.soccerMenu) { this.soccerMenu = new SoccerMenu(this.scene, this.xr, this.controllers); } } private downloadgltf() { exportGltf(this.scene); } }