Updated text input to work on desktop Mac

This commit is contained in:
Michael Mainguy 2023-07-27 08:51:25 -05:00
parent af2227b746
commit c74dc62654
12 changed files with 160 additions and 172 deletions

View File

@ -30,10 +30,9 @@ import log from "loglevel";
export class App {
//preTasks = [havokModule];
public static scene: Scene;
public static xr: WebXRDefaultExperience;
public static rig: Rigplatform;
public static gamepad: Gamepad;
private scene: Scene;
private xr: WebXRDefaultExperience;
private rig: Rigplatform;
constructor() {
log.setLevel('debug');
@ -49,13 +48,13 @@ export class App {
}
async initialize(canvas) {
if (App.xr) {
App.xr.dispose();
App.xr = null;
if (this.xr) {
this.xr.dispose();
this.xr = null;
}
if (App.scene) {
App.scene.dispose();
App.scene = null;
if (this.scene) {
this.scene.dispose();
this.scene = null;
}
if (DiagramManager.onDiagramEventObservable) {
DiagramManager.onDiagramEventObservable.clear();
@ -64,7 +63,7 @@ export class App {
const engine = new Engine(canvas, true);
const scene = new Scene(engine);
App.scene = scene;
this.scene = scene;
const havokInstance = await HavokPhysics();
@ -82,7 +81,7 @@ export class App {
'./outdoor_field.jpeg', {},
scene);
const ground = this.createGround();
App.xr = await WebXRDefaultExperience.CreateAsync(scene, {
this.xr = await WebXRDefaultExperience.CreateAsync(scene, {
floorMeshes: [ground],
disableTeleportation: true,
outputCanvasOptions: {
@ -96,9 +95,9 @@ export class App {
}
});
App.xr.baseExperience.onStateChangedObservable.add((state) => {
this.xr.baseExperience.onStateChangedObservable.add((state) => {
if (state == WebXRState.IN_XR) {
App.xr.baseExperience.camera.position = new Vector3(0, 1.6, 0);
this.xr.baseExperience.camera.position = new Vector3(0, 1.6, 0);
window.addEventListener(('pa-button-state-change'), (event: any) => {
if (event.detail) {
log.debug('App', event.detail);
@ -108,11 +107,11 @@ export class App {
}
});
const diagramManager = new DiagramManager(App.scene, App.xr.baseExperience);
App.rig = new Rigplatform(App.scene, App.xr);
const toolbox = new Toolbox(scene, App.xr.baseExperience);
const diagramManager = new DiagramManager(this.scene, this.xr.baseExperience);
this.rig = new Rigplatform(this.scene, this.xr);
const toolbox = new Toolbox(scene, this.xr.baseExperience);
App.scene.gamepadManager.onGamepadConnectedObservable.add((gamepad) => {
this.scene.gamepadManager.onGamepadConnectedObservable.add((gamepad) => {
try {
const dualshock = (gamepad as DualShockPad);
@ -197,18 +196,18 @@ export class App {
}
createGround() {
const groundMaterial = new PBRMetallicRoughnessMaterial("groundMaterial", App.scene);
const gText = new Texture("./grass1.jpeg", App.scene);
const groundMaterial = new PBRMetallicRoughnessMaterial("groundMaterial", this.scene);
const gText = new Texture("./grass1.jpeg", this.scene);
gText.uScale = 40;
gText.vScale = 40;
groundMaterial.baseTexture = gText;
groundMaterial.metallic = 0;
groundMaterial.roughness = 1;
const ground = MeshBuilder.CreateGround("ground", {width: 100, height: 100, subdivisions: 1}, App.scene);
const ground = MeshBuilder.CreateGround("ground", {width: 100, height: 100, subdivisions: 1}, this.scene);
ground.material = groundMaterial;
new PhysicsAggregate(ground, PhysicsShapeType.BOX, {mass: 0}, App.scene);
new PhysicsAggregate(ground, PhysicsShapeType.BOX, {mass: 0}, this.scene);
return ground;
}
}

View File

@ -1,30 +1,7 @@
import {AbstractMesh, Observable, TransformNode} from "@babylonjs/core";
export enum ControllerMovementMode {
ROTATE,
TRANSLATE
}
export class MeshHoverEvent {
public readonly mesh: AbstractMesh;
public readonly pointerId: string;
public readonly pointerMeshId: string;
public readonly isHovered: boolean;
constructor(mesh: AbstractMesh, isHovered: boolean, pointerId: string, pointerMeshId: string) {
this.mesh = mesh;
this.isHovered = isHovered;
this.pointerId = pointerId;
this.pointerMeshId = pointerMeshId;
}
}
export class Controllers {
public static movable: TransformNode | AbstractMesh;
public static controllerObserver = new Observable();
public static movementMode: ControllerMovementMode = ControllerMovementMode.ROTATE;
public static toggleMovementMode() {
if (this.movementMode == ControllerMovementMode.ROTATE) {
this.movementMode = ControllerMovementMode.TRANSLATE;
} else {
this.movementMode = ControllerMovementMode.ROTATE;
}
}
}

View File

@ -1,13 +1,12 @@
import {Base} from "./base";
import {
Angle,
Scene,
Vector3,
WebXRControllerComponent,
WebXRDefaultExperience,
WebXRInputSource
} from "@babylonjs/core";
import {ControllerMovementMode, Controllers} from "./controllers";
import {Controllers} from "./controllers";
import log from "loglevel";
export class Right extends Base {
@ -26,6 +25,7 @@ export class Right extends Base {
}
private initBButton(bbutton: WebXRControllerComponent) {
if (bbutton) {
bbutton.onButtonStateChangedObservable.add((button) => {
@ -62,15 +62,7 @@ export class Right extends Base {
if (thumbstick) {
thumbstick.onAxisValueChangedObservable.add((value) => {
log.trace('Right', `thumbstick moved ${value.x}, ${value.y}`);
if (!Controllers.movable) {
this.moveRig(value);
} else {
if (Controllers.movementMode == ControllerMovementMode.ROTATE) {
this.rotateMovable(value);
} else {
this.moveMovable(value);
}
}
this.moveRig(value);
});
thumbstick.onButtonStateChangedObservable.add((value) => {
if (value.pressed) {
@ -98,32 +90,4 @@ export class Right extends Base {
Controllers.controllerObserver.notifyObservers({type: 'updown', value: 0});
}
}
private rotateMovable(value: { x: number; y: number }) {
if (Math.abs(value.y) > .1) {
Controllers.movable.rotation.x +=
Angle.FromDegrees(Math.sign(value.y)).radians();
Controllers.movable.rotation.x = this.fixRadians(Controllers.movable.rotation.x);
}
if (Math.abs(value.x) > .1) {
Controllers.movable.rotation.z +=
Angle.FromDegrees(Math.sign(value.x)).radians();
Controllers.movable.rotation.z = this.fixRadians(Controllers.movable.rotation.z);
}
}
private fixRadians(value: number) {
if (value > 2 * Math.PI) {
return value - 2 * Math.PI;
} else {
return value;
}
}
private moveMovable(value: { x: number; y: number }) {
if (Math.abs(value.y) > .1) {
Controllers.movable.position.z += Math.sign(value.y) * -.005;
}
if (Math.abs(value.x) > .1) {
Controllers.movable.position.x += Math.sign(value.x) * .005;
}
}
}

View File

@ -10,7 +10,7 @@ import {
PhysicsShapeType,
Quaternion,
Scene,
StandardMaterial, TransformNode,
StandardMaterial,
Vector3,
WebXRDefaultExperience
} from "@babylonjs/core";
@ -26,7 +26,7 @@ export class Rigplatform {
private velocityIndex = 2;
private readonly velocityArray = [0.01, 0.1, 1, 2, 5];
public bMenu: Bmenu;
private scene: Scene;
private readonly scene: Scene;
public static instance: Rigplatform;
private static xr: WebXRDefaultExperience;
private yRotation: number = 0;

View File

@ -1,4 +1,4 @@
import {AbstractMesh, Color3, Material, Observable, Scene, WebXRExperienceHelper} from "@babylonjs/core";
import {Observable, Scene, WebXRExperienceHelper} from "@babylonjs/core";
import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity";
import {IPersistenceManager} from "./persistenceManager";
@ -6,41 +6,36 @@ import {IndexdbPersistenceManager} from "./indexdbPersistenceManager";
import {MeshConverter} from "./meshConverter";
import log from "loglevel";
export class DiagramManager {
private persistenceManager: IPersistenceManager = new IndexdbPersistenceManager("diagram");
static onDiagramEventObservable: Observable<DiagramEvent> = new Observable();
private readonly scene: Scene;
private xr: WebXRExperienceHelper;
static currentMesh: AbstractMesh;
private materialMap: Map<string, Material> = new Map<string, Material>();
constructor(scene: Scene, xr: WebXRExperienceHelper) {
this.scene = scene;
this.xr = xr;
this.persistenceManager.updateObserver.add(this.onRemoteEvent, -1, true, this);
log.debug('DiagramManager', "remove event observer added");
log.getLogger('DiagramManager').debug( "remote event observer added");
this.persistenceManager.initialize();
if (!DiagramManager.onDiagramEventObservable) {
log.debug('DiagramManager', "onDiagramEventObservable missing, recreated");
log.getLogger('DiagramManager').debug( "onDiagramEventObservable missing, recreated");
DiagramManager.onDiagramEventObservable = new Observable();
}
if (DiagramManager.onDiagramEventObservable.hasObservers()) {
log.warn('DiagramManager', "onDiagramEventObservable already has Observers, this shouldn't happen");
log.getLogger('DiagramManager').debug("onDiagramEventObservable already has Observers, this shouldn't happen");
} else {
DiagramManager.onDiagramEventObservable.add(this.onDiagramEvent, -1, true, this);
log.debug('DiagramManager', "onDiagramEventObservable Observer added");
log.getLogger('DiagramManager').debug( "onDiagramEventObservable Observer added");
}
log.debug('DiagramManager', "DiagramManager constructed");
log.getLogger('DiagramManager').debug( "DiagramManager constructed");
}
private onRemoteEvent(event: DiagramEntity) {
//const mesh = Toolbox.instance.newMesh(ToolType[Object.entries(ToolType).find(e => e[1] == event.template)[0]], event.id);
log.debug('DiagramManager', event);
log.getLogger('DiagramManager').debug(event);
const mesh = MeshConverter.fromDiagramEntity(event, this.scene);
if (event.parent) {
mesh.parent = this.scene.getMeshById(event.parent);
@ -48,6 +43,7 @@ export class DiagramManager {
}
private onDiagramEvent(event: DiagramEvent) {
log.getLogger("DiagramManager").debug(event);
const entity = event.entity;
let mesh;
if (entity) {

View File

@ -0,0 +1,46 @@
import {AbstractMesh, Angle, MeshBuilder, Scene, WebXRExperienceHelper} from "@babylonjs/core";
import {AdvancedDynamicTexture, InputText} from "@babylonjs/gui";
export class InputTextView {
private mesh: AbstractMesh;
private scene: Scene;
private xr: WebXRExperienceHelper;
private inputPlane: AbstractMesh;
private inputText: InputText;
constructor(scene: Scene, xr: WebXRExperienceHelper, mesh: AbstractMesh ) {
this.scene = scene;
this.xr = xr;
this.mesh = mesh;
}
public async show(text: string) {
this.inputPlane = MeshBuilder.CreatePlane("myPlane", {width: 1, height: .125}, this.scene);
const pos = this.mesh.absolutePosition;
pos.y += .2;
this.inputPlane.position= pos;
this.inputPlane.rotation.y = Angle.FromDegrees(180).radians();
const textDisplayTexture = AdvancedDynamicTexture.CreateForMesh(this.inputPlane, 1024, 128);
this.inputPlane.material.backFaceCulling = false;
this.inputText = this.createInputText();
this.inputText.text = text;
textDisplayTexture.addControl(this.inputText);
}
private createInputText(): InputText {
const inputText = new InputText("input");
inputText.color= "white";
inputText.background = "black";
inputText.height= "128px";
inputText.width= "1024px";
inputText.maxWidth= "1024px";
inputText.margin="0px";
inputText.fontSize= "48px";
return inputText;
}
public async dispose() {
this.inputPlane.dispose(false, true);
this.inputPlane = null;
this.inputText = null;
}
public async updateText(text: string) {
this.inputText.text = text;
}
}

View File

@ -13,10 +13,7 @@ export class Cameras {
}
public async getCameras() {
const cameras = await axios.get('https://local.immersiveidea.com/api/cameras');
this.cameras = cameras;
//console.log(cameras);
this.cameras = await axios.get('https://local.immersiveidea.com/api/cameras');
}
public createCameras() {

View File

@ -1,34 +1,27 @@
import {
Angle,
AbstractMesh,
GizmoManager,
MeshBuilder,
PointerEventTypes,
Scene,
Vector3,
WebXRExperienceHelper
} from "@babylonjs/core";
import {
AdvancedDynamicTexture,
Button3D,
ColorPicker,
GUI3DManager,
InputText,
StackPanel3D,
TextBlock
} from "@babylonjs/gui";
import {Button3D, GUI3DManager, StackPanel3D, TextBlock} from "@babylonjs/gui";
import {DiagramManager} from "../diagram/diagramManager";
import {BmenuState} from "./MenuState";
import {DiagramEvent, DiagramEventType} from "../diagram/diagramEntity";
import {MeshConverter} from "../diagram/meshConverter";
import log from "loglevel";
import {InputTextView} from "../information/inputTextView";
export class Bmenu {
private state: BmenuState = BmenuState.NONE;
private manager: GUI3DManager;
private readonly scene: Scene;
private textView: InputTextView;
private textInput: HTMLElement;
private gizmoManager: GizmoManager;
private xr: WebXRExperienceHelper;
private textInput: any;
constructor(scene: Scene, xr: WebXRExperienceHelper) {
@ -47,7 +40,21 @@ export class Bmenu {
case PointerEventTypes.POINTERPICK:
if (pointerInfo.pickInfo?.pickedMesh?.metadata?.template &&
pointerInfo.pickInfo?.pickedMesh?.parent?.parent?.id != "toolbox") {
if (this.textInput) {
this.textInput.blur();
this.textInput.remove();
this.textInput = null;
}
if (this.textView) {
this.textView.dispose().then(() => {
log.getLogger("bmenu").debug("disposed");
}).catch((e) => {
log.getLogger("bmenu").error(e);
});
this.textView = null;
}
switch (this.state) {
case BmenuState.REMOVING:
log.debug("removing " + pointerInfo.pickInfo.pickedMesh.id);
const event: DiagramEvent = {
@ -82,40 +89,51 @@ export class Bmenu {
case BmenuState.LABELING:
const mesh = pointerInfo.pickInfo.pickedMesh;
log.debug("labeling " + mesh.id);
/* const myPlane = MeshBuilder.CreatePlane("myPlane", {width: 1, height: .125}, this.scene);
//myPlane.parent=mesh;
const pos = mesh.absolutePosition;
pos.y += .2;
myPlane.position= pos;
myPlane.rotation.y = Angle.FromDegrees(180).radians();
const advancedTexture2 = AdvancedDynamicTexture.CreateForMesh(myPlane, 1024, 128);
myPlane.material.backFaceCulling = false;
const inputText = new InputText("input");
inputText.color= "white";
inputText.background = "black";
inputText.height= "128px";
inputText.width= "1024px";
inputText.maxWidth= "1024px";
inputText.margin="0px";
inputText.fontSize= "48px";
advancedTexture2.addControl(inputText);
*/
const textInput = document.createElement("input");
textInput.type = "text";
document.body.appendChild(textInput);
textInput.value = "";
if (mesh?.metadata?.text) {
textInput.value = mesh.metadata.text;
} else {
textInput.value = "";
}
textInput.focus();
textInput.addEventListener('input', (event)=> {
log.debug(event);
});
textInput.addEventListener('keydown', (event)=> {
log.debug(event);
if (event.key == "Enter") {
textInput.blur();
textInput.remove();
}
});
if (navigator.userAgent.indexOf('Macintosh') > -1) {
textInput.addEventListener('input', (event)=> {
log.debug(event);
});
const textView = new InputTextView(this.scene, this.xr, mesh)
textView.show(textInput.value);
textInput.addEventListener('keydown', (event)=> {
if (event.key == "Enter") {
this.persist(mesh, textInput.value);
textInput.blur();
textInput.remove();
this.textView.dispose();
this.textView = null;
this.textInput = null;
} else {
textView.updateText(textInput.value);
}
});
this.textView = textView;
} else {
textInput.addEventListener('keydown', (event)=> {
log.debug(event);
if (event.key == "Enter") {
this.persist(mesh, textInput.value);
textInput.blur();
textInput.remove();
this.textInput = null;
this.textView = null;
}
});
}
this.textInput = textInput;
break;
}
@ -124,7 +142,17 @@ export class Bmenu {
}
});
}
private persist(mesh: AbstractMesh, text: string) {
if (mesh.metadata) {
mesh.metadata.text = text;
} else {
log.getLogger('bmenu').error("mesh has no metadata");
}
DiagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.MODIFY,
entity: MeshConverter.toDiagramEntity(mesh),
});
}
makeButton(name: string, id: string) {
const button = new Button3D(name);
button.scaling = new Vector3(.1, .1, .1);
@ -133,18 +161,10 @@ export class Bmenu {
text.fontSize = "24px";
text.color = "white";
button.content = text;
button.onPointerClickObservable.add(this.#clickhandler, -1, false, this);
button.onPointerClickObservable.add(this.handleClick, -1, false, this);
return button;
}
public getState() {
return this.state;
}
public setState(state: BmenuState) {
this.state = state;
}
toggle() {
//console.log(mesh.name);
if (this.manager) {
@ -167,7 +187,7 @@ export class Bmenu {
}
}
#clickhandler(_info, state) {
private handleClick(_info, state) {
switch (state.currentTarget.name) {
case "modify":
this.state = BmenuState.MODIFYING;

View File

@ -1,16 +1,9 @@
import {AbstractMesh, MeshBuilder, Scene, Vector3, WebXRExperienceHelper} from "@babylonjs/core";
import {AbstractMesh, MeshBuilder, Scene, WebXRExperienceHelper} from "@babylonjs/core";
import {
AdvancedDynamicTexture,
Button3D,
GUI3DManager, InputText, PlanePanel,
StackPanel, StackPanel3D,
TextBlock,
TouchHolographicButton
AdvancedDynamicTexture
} from "@babylonjs/gui";
import {MyMenu} from "../util/myMenu";
export class Keyboard {
private manager: GUI3DManager;
private readonly scene: Scene;
private mesh: AbstractMesh;
private panel: AbstractMesh;

View File

@ -34,7 +34,7 @@ export class Toolbox {
public readonly node : TransformNode;
private readonly manager: GUI3DManager;
private readonly gridsize = 5;
private addPanel: StackPanel3D;
private readonly addPanel: StackPanel3D;
constructor (scene:Scene, xr: WebXRExperienceHelper) {
this.scene = scene;
this.addPanel = new StackPanel3D();
@ -145,9 +145,6 @@ export class Toolbox {
this.buildColor(Color3.FromHexString(color));
}
}
private nextPosition() {
}
public buildTool(tool: ToolType, parent: AbstractMesh) {
let newItem: Mesh;

View File

@ -1,5 +1,6 @@
import {Angle, Color3, MeshBuilder, Scene, StandardMaterial, Texture} from "@babylonjs/core";
import googleStaticMapsTile from "google-static-maps-tile";
import log from "loglevel";
export class Gmap {
private readonly scene: Scene;
@ -9,6 +10,7 @@ export class Gmap {
}
public async createMapTiles(lat, lon) {
log.debug('createMapTiles', lat, lon)
googleStaticMapsTile({
areaSize: '2560x2560',
center: '26.443397,-82.111512',
@ -19,10 +21,7 @@ export class Gmap {
maptype: 'satellite'
})
.on('progress', function (info) {
//console.log(info.count);
//console.log(info.total);
const image = info.image;
image.style.position = 'absolute';
image.style.left = info.data.x + 'px';
image.style.top = info.data.y + 'px';

View File

@ -1,4 +1,4 @@
import {PlanePanel, TouchHolographicMenu} from "@babylonjs/gui";
import {PlanePanel} from "@babylonjs/gui";
export class MyMenu extends PlanePanel {
public arrangeChildren: boolean = true;