From cabc38ce09dff6a5930200e1851d508af90dbec0 Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Fri, 12 Apr 2024 07:04:02 -0500 Subject: [PATCH] Simplified interactions, changed menu interactions for changing entities. --- src/controllers/base.ts | 11 ++- src/diagram/diagramManager.ts | 21 +++++ src/information/inputTextView.ts | 145 ++++++++++++------------------- src/menus/clickMenu.ts | 3 + src/objects/handle.ts | 20 +++-- src/toolbox/toolbox.ts | 35 ++++++-- src/util/displayDebug.ts | 77 ++++++++++++++++ 7 files changed, 207 insertions(+), 105 deletions(-) create mode 100644 src/util/displayDebug.ts diff --git a/src/controllers/base.ts b/src/controllers/base.ts index 97f3b99..c6adc2b 100644 --- a/src/controllers/base.ts +++ b/src/controllers/base.ts @@ -21,6 +21,7 @@ import {snapRotateVal} from "../util/functions/snapRotateVal"; import {grabAndClone} from "./functions/grab"; import {isDiagramEntity} from "../diagram/functions/isDiagramEntity"; import {ClickMenu} from "../menus/clickMenu"; +import {displayDebug} from "../util/displayDebug"; const CLICK_TIME = 300; export class Base { @@ -144,11 +145,12 @@ export class Base { private grab(cloneEntity: boolean = false) { let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.controller.uniqueId); + if (!mesh) { return; } let player = false; - + displayDebug(mesh); if (!isDiagramEntity(mesh)) { if (this.handleWasGrabbed(mesh)) { mesh && mesh.setParent(this.controller.motionController.rootMesh); @@ -215,7 +217,12 @@ export class Base { return; } if (this.handleWasGrabbed(mesh)) { - mesh.setParent(this.scene.getMeshByName("platform")) + mesh.setParent(this.scene.getMeshByName("platform")); + const location = { + position: {x: mesh.position.x, y: mesh.position.y, z: mesh.position.z}, + rotation: {x: mesh.rotation.x, y: mesh.rotation.y, z: mesh.rotation.z} + } + localStorage.setItem(mesh.id, JSON.stringify(location)); return; } reparent(mesh, this.previousParentId, this.grabbedMeshParentId); diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts index 38fa78d..b64dd21 100644 --- a/src/diagram/diagramManager.ts +++ b/src/diagram/diagramManager.ts @@ -14,6 +14,7 @@ import {toDiagramEntity} from "./functions/toDiagramEntity"; import {v4 as uuidv4} from 'uuid'; import {buildEntityActionManager} from "./functions/buildEntityActionManager"; import {isDiagramEntity} from "./functions/isDiagramEntity"; +import {InputTextView} from "../information/inputTextView"; export class DiagramManager { @@ -21,6 +22,7 @@ export class DiagramManager { private readonly _controllers: Controllers; private readonly diagramEntityActionManager: ActionManager; private presentationManager: PresentationManager; + private readonly inputTextView: InputTextView; public readonly onDiagramEventObservable: Observable = new Observable(); private readonly logger = log.getLogger('DiagramManager'); @@ -31,6 +33,21 @@ export class DiagramManager { constructor(scene: Scene) { this._config = new AppConfig(); this._controllers = new Controllers(); + this.inputTextView = new InputTextView(scene, this._controllers); + this.inputTextView.onTextObservable.add((evt) => { + const mesh = scene.getMeshById(evt.id); + if (mesh) { + const entity = toDiagramEntity(mesh); + entity.text = evt.text; + this.onDiagramEventObservable.notifyObservers({ + type: DiagramEventType.MODIFY, + entity: entity + }, -1); + } else { + this.logger.error("mesh not found", evt.id); + } + }); + this.sounds = new DiaSounds(scene); this.scene = scene; this.toolbox = new Toolbox(scene); @@ -67,6 +84,10 @@ export class DiagramManager { }); } + public editText(mesh: AbstractMesh) { + this.inputTextView.show(mesh); + } + public get controllers(): Controllers { return this._controllers; } diff --git a/src/information/inputTextView.ts b/src/information/inputTextView.ts index 3820f97..3f5c3b8 100644 --- a/src/information/inputTextView.ts +++ b/src/information/inputTextView.ts @@ -1,63 +1,84 @@ -import {MeshBuilder, Observable, Scene, Vector3, WebXRDefaultExperience} from "@babylonjs/core"; +import {AbstractMesh, MeshBuilder, Observable, Scene, Vector3} from "@babylonjs/core"; import log, {Logger} from "loglevel"; import {AdvancedDynamicTexture, Control, InputText, VirtualKeyboard} from "@babylonjs/gui"; import {ControllerEventType, Controllers} from "../controllers/controllers"; -import {setMenuPosition} from "../util/functions/setMenuPosition"; import {DiaSounds} from "../util/diaSounds"; import {Handle} from "../objects/handle"; export type TextEvent = { + id: string; text: string; } export class InputTextView { public readonly onTextObservable: Observable = new Observable(); - private readonly text: string = ""; private readonly scene: Scene; + private readonly inputMesh: AbstractMesh; private sounds: DiaSounds; private readonly controllers: Controllers; - private readonly xr: WebXRDefaultExperience; private readonly logger: Logger = log.getLogger('InputTextView'); + private readonly handle: Handle; + private inputText: InputText; + private diagramMesh: AbstractMesh; - constructor(text: string, xr: WebXRDefaultExperience, scene: Scene, controllers: Controllers) { - this.text = text ? text : ""; - this.xr = xr; + constructor(scene: Scene, controllers: Controllers) { this.controllers = controllers; this.scene = scene; this.sounds = new DiaSounds(scene); + this.inputMesh = MeshBuilder.CreatePlane("input", {width: 1, height: .5}, this.scene); + this.handle = new Handle(this.inputMesh); + this.createKeyboard(); } - public show() { - this.showVirtualKeyboard(); - /*if ((this.xr as WebXRDefaultExperience).baseExperience?.sessionManager?.inXRSession) { - this.showXr(); + public show(mesh: AbstractMesh) { + this.inputText.text = mesh.metadata?.label || ""; + this.handle.mesh.setEnabled(true); + this.diagramMesh = mesh; + console.log(mesh.metadata); + } + + public createKeyboard() { + const platform = this.scene.getMeshById('platform'); + let position = new Vector3(0, 1.66, .5); + let rotation = new Vector3(0, .9, 0); + const handle = this.handle; + if (handle.mesh.position.x != 0 && handle.mesh.position.y != 0 && handle.mesh.position.z != 0) { + position = handle.mesh.position; + } + if (handle.mesh.rotation.x != 0 && handle.mesh.rotation.y != 0 && handle.mesh.rotation.z != 0) { + rotation = handle.mesh.rotation; + } + if (!platform) { + this.scene.onNewMeshAddedObservable.add((mesh) => { + if (mesh.id == 'platform') { + this.logger.debug("platform added"); + handle.mesh.setParent(platform); + + handle.mesh.position = position; + handle.mesh.rotation = rotation; + } + }); } else { - this.showWeb(); - }*/ - } - - public showVirtualKeyboard() { - const inputMesh = MeshBuilder.CreatePlane("input", {width: 1, height: .5}, this.scene); - const handle = new Handle(inputMesh); - setMenuPosition(handle.mesh, this.scene, new Vector3(0, .4, 0)); - const advancedTexture = AdvancedDynamicTexture.CreateForMesh(inputMesh, 2048, 1024, false); + handle.mesh.parent = platform; + handle.mesh.position = position; + handle.mesh.rotation = rotation; + } + //setMenuPosition(handle.mesh, this.scene, new Vector3(0, .4, 0)); + const advancedTexture = AdvancedDynamicTexture.CreateForMesh(this.inputMesh, 2048, 1024, false); const input = new InputText(); - input.width = 0.5; input.maxWidth = 0.5; input.height = "64px"; - input.text = this.text; - + input.text = ""; input.fontSize = "32px"; input.color = "white"; input.background = "black"; input.thickness = 3; input.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP; - advancedTexture.addControl(input); - + this.inputText = input; + advancedTexture.addControl(this.inputText); const keyboard = VirtualKeyboard.CreateDefaultLayout(); - keyboard.scaleY = 2; keyboard.scaleX = 2; keyboard.transformCenterY = 0; @@ -84,77 +105,21 @@ export class InputTextView { }, -1, false, this, false); }); - keyboard.onPointerDownObservable.add(() => { this.sounds.tick.play(); }); keyboard.onKeyPressObservable.add((key) => { if (key === '↵') { - this.onTextObservable.notifyObservers({text: input.text}); - input.dispose(); - keyboard.dispose(); - advancedTexture.dispose(); - inputMesh.dispose(); - this.sounds.exit.play(); + this.logger.error(this.inputText.text); + this.onTextObservable.notifyObservers({id: this.diagramMesh.id, text: this.inputText.text}); + this.hide(); } - }); - - - this.sounds.enter.play(); + }, -1, false, this, false); + this.handle.mesh.setEnabled(false); } - public showWeb() { - const textInput = new InputText('identity', this.text); - textInput.width = 0.2; - textInput.height = "40px"; - textInput.color = "white"; - textInput.background = "black"; - textInput.focusedBackground = "black"; - - const advancedTexture = AdvancedDynamicTexture.CreateFullscreenUI("myUI"); - advancedTexture.addControl(textInput); - textInput.onKeyboardEventProcessedObservable.add((evt) => { - if (evt.key === 'Enter') { - this.onTextObservable.notifyObservers({text: textInput.text}); - textInput.dispose(); - advancedTexture.dispose(); - - } - - }); - } - - private showXr() { - const textInput = document.createElement("input"); - textInput.type = "text"; - document.body.appendChild(textInput); - textInput.value = this.text; - this.controllers.controllerObserver.notifyObservers({type: ControllerEventType.HIDE}); - - /*if (this.xr?.baseExperience?.sessionManager?.inXRSession) { - this.xr.input.controllers.forEach((controller) => { - controller.motionController.rootMesh.setEnabled(false); - controller.pointer.setEnabled(false); - }); - - }*/ - - textInput.addEventListener('blur', () => { - log.getLogger('bmenu').debug("blur"); - this.onTextObservable.notifyObservers({text: textInput.value}); - this.controllers.controllerObserver.notifyObservers({type: ControllerEventType.SHOW}); - - /*if (this.xr?.baseExperience?.sessionManager?.inXRSession) { - this.xr.input.controllers.forEach((controller) => { - controller.motionController.rootMesh.setEnabled(true); - controller.pointer.setEnabled(true); - }); - }*/ - textInput.blur(); - textInput.remove(); - }); - - textInput.focus(); - + private hide() { + this.handle.mesh.setEnabled(false); + this.diagramMesh = null; } } \ No newline at end of file diff --git a/src/menus/clickMenu.ts b/src/menus/clickMenu.ts index dfdeec8..9cd3ee4 100644 --- a/src/menus/clickMenu.ts +++ b/src/menus/clickMenu.ts @@ -63,6 +63,9 @@ export class ClickMenu { this.dispose(); break; case "label": + this.diagramManager.editText(this.entity); + this.dispose(); + break; } diff --git a/src/objects/handle.ts b/src/objects/handle.ts index 892e8cc..78cb8c9 100644 --- a/src/objects/handle.ts +++ b/src/objects/handle.ts @@ -13,13 +13,23 @@ export class Handle { private buildHandle() { const scene: Scene = this.transformNode.getScene(); const handle = getHandleMesh("handle-" + this.transformNode.id + "-mesh", scene); - - handle.position = Vector3.Zero(); - handle.metadata = {handle: true}; if (this.transformNode) { this.transformNode.setParent(handle); - //this.transformNode.rotation.y = Math.PI; } + const stored = localStorage.getItem(handle.id); + if (stored) { + try { + const locationdata = JSON.parse(stored); + handle.position = new Vector3(locationdata.position.x, locationdata.position.y, locationdata.position.z); + handle.rotation = new Vector3(locationdata.rotation.x, locationdata.rotation.y, locationdata.rotation.z); + } catch (e) { + console.error(e); + handle.position = Vector3.Zero(); + } + } else { + handle.position = Vector3.Zero(); + } + handle.metadata = {handle: true}; this.mesh = handle; } } @@ -40,6 +50,6 @@ function getHandleMesh(name: string, scene: Scene): InstancedMesh { handle.material = buildStandardMaterial('base-handle-material', scene, "#CCCCDD"); handle.id = "base-handle-mesh"; const instance = new InstancedMesh(name, (handle as Mesh)); - instance.setParent(scene.getMeshByName("platform")); + instance.setParent(scene.getMeshById("platform")); return instance; } \ No newline at end of file diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index abb6c97..e935077 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -11,6 +11,7 @@ const colors: string[] = [ "#1e90ff", "#98fb98", "#ffe4b5", "#ff69b4" ] + export class Toolbox { private readonly logger = log.getLogger('Toolbox'); private index = 0; @@ -18,6 +19,7 @@ export class Toolbox { private readonly scene: Scene; private colorPicker: TransformNode; private changing = false; + private readonly handle: Handle; private readonly manager: GUI3DManager; private readonly addPanel: StackPanel3D; public readonly colorChangeObservable: Observable<{ oldColor: string, newColor: string }> = @@ -30,7 +32,7 @@ export class Toolbox { this.manager = new GUI3DManager(scene); this.manager.addControl(this.addPanel); this.toolboxBaseNode = new TransformNode("toolbox", this.scene); - new Handle(this.toolboxBaseNode); + this.handle = new Handle(this.toolboxBaseNode); this.toolboxBaseNode.position.y = .2; //this.toolboxBaseNode.position.z = .05; /*this.axes = new AxesViewer(this.scene); @@ -91,19 +93,36 @@ export class Toolbox { } } //this.toolboxBaseNode.parent.setEnabled(false); - const offset = new Vector3(0, 1, 1); + let offset = new Vector3(-.50, 1.6, .38); + let rotation = new Vector3(.5, -.6, .18); + if (this.toolboxBaseNode.parent) { const platform = this.scene.getNodeById("platform"); + if (platform) { - const handle = (this.toolboxBaseNode.parent as TransformNode); - handle.parent = platform; - handle.position = offset; + const handle = this.handle; + if (handle.mesh.position.x != 0 && handle.mesh.position.y != 0 && handle.mesh.position.z != 0) { + offset = handle.mesh.position; + } + if (handle.mesh.rotation.x != 0 && handle.mesh.rotation.y != 0 && handle.mesh.rotation.z != 0) { + rotation = handle.mesh.rotation; + } + handle.mesh.parent = platform; + handle.mesh.position = offset; + handle.mesh.rotation = rotation; } else { this.scene.onNewMeshAddedObservable.add((mesh: AbstractMesh) => { if (mesh.id == "platform") { - const handle = (this.toolboxBaseNode.parent as TransformNode); - handle.parent = mesh; - handle.position = offset; + const handle = this.handle; + if (handle.mesh.position.x != 0 && handle.mesh.position.y != 0 && handle.mesh.position.z != 0) { + offset = handle.mesh.position; + } + if (handle.mesh.rotation.x != 0 && handle.mesh.rotation.y != 0 && handle.mesh.rotation.z != 0) { + rotation = handle.mesh.rotation; + } + handle.mesh.parent = mesh; + handle.mesh.position = offset; + handle.mesh.rotation = rotation; } }); } diff --git a/src/util/displayDebug.ts b/src/util/displayDebug.ts new file mode 100644 index 0000000..4763e1b --- /dev/null +++ b/src/util/displayDebug.ts @@ -0,0 +1,77 @@ +import { + AbstractMesh, + DynamicTexture, + Material, + MeshBuilder, + StandardMaterial, + TransformNode, + Vector3 +} from "@babylonjs/core"; + +const debug = false; + +export function displayDebug(transform: TransformNode) { + if (debug) { + const position = `position: (${transform.position.x.toFixed(2)}, ${transform.position.y.toFixed(2)}, ${transform.position.z.toFixed(2)})`; + const rotation = `rotation: (${transform.rotation.x.toFixed(2)}, ${transform.rotation.y.toFixed(2)}, ${transform.rotation.z.toFixed(2)})`; + buildText(position, transform, new Vector3(0, 1.5, 1)); + buildText(rotation, transform, new Vector3(0, 1.4, 1)); + } + + +} + +function buildText(text: string, transform: TransformNode, position) { + const height = 0.05; + const font_size = 24; + const font = "bold " + font_size + "px Arial"; + //Set height for dynamic texture + const DTHeight = 1.5 * font_size; //or set as wished + //Calc Ratio + const ratio = height / DTHeight; + + //Use a temporary dynamic texture to calculate the length of the text on the dynamic texture canvas + const temp = new DynamicTexture("DynamicTexture", 32, transform.getScene()); + const tmpctx = temp.getContext(); + tmpctx.font = font; + const DTWidth = tmpctx.measureText(text).width + 8; + + //Calculate width the plane has to be + const planeWidth = DTWidth * ratio; + + //Create dynamic texture and write the text + const dynamicTexture = new DynamicTexture("DynamicTexture", { + width: DTWidth, + height: DTHeight + }, transform.getScene(), false); + const mat = new StandardMaterial("mat", transform.getScene()); + mat.diffuseTexture = dynamicTexture; + //mat.emissiveColor = Color3.White(); + dynamicTexture.drawText(text, null, null, font, "#000000", "#ffffff", true); + //Create plane and set dynamic texture as material + //const plane = MeshBuilder.CreatePlane("text" + text, {width: planeWidth, height: height}, mesh.getScene()); + const plane1 = createPlane(mat, transform, text, planeWidth, height, position); + const plane2 = createPlane(mat, transform, text, planeWidth, height, position); + plane2.rotation.y = Math.PI; +} + +function createPlane(mat: Material, transform: TransformNode, text: string, planeWidth: number, height: number, position): AbstractMesh { + const plane = MeshBuilder.CreatePlane("text" + text, {width: planeWidth, height: height}, transform.getScene()); + + plane.material = mat; + //plane.billboardMode = Mesh.BILLBOARDMODE_ALL; + plane.metadata = {exportable: false, label: false}; + + //const yOffset = mesh.getBoundingInfo().boundingSphere.maximum.y; + //plane.parent = mesh; + //plane.scaling.y = (1 / mesh.scaling.y); + //plane.scaling.x = (1 / mesh.scaling.x); + //plane.scaling.z = (1 / mesh.scaling.z); + //plane.position = transform.position.y = yOffset + (height * plane.scaling.y); + plane.position = position; + window.setTimeout(() => { + plane.dispose(); + }, 5000); + return plane; + +}