Simplified interactions, changed menu interactions for changing entities.

This commit is contained in:
Michael Mainguy 2024-04-12 07:04:02 -05:00
parent 727977d5c6
commit cabc38ce09
7 changed files with 207 additions and 105 deletions

View File

@ -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);

View File

@ -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<DiagramEvent> = 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;
}

View File

@ -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<TextEvent> = new Observable<TextEvent>();
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();
}*/
handle.mesh.parent = platform;
handle.mesh.position = position;
handle.mesh.rotation = rotation;
}
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);
//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;
}
}

View File

@ -63,6 +63,9 @@ export class ClickMenu {
this.dispose();
break;
case "label":
this.diagramManager.editText(this.entity);
this.dispose();
break;
}

View File

@ -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;
}

View File

@ -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;
}
});
}

77
src/util/displayDebug.ts Normal file
View File

@ -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;
}