Refactored diagram manager and loggers.

This commit is contained in:
Michael Mainguy 2023-07-28 09:30:10 -05:00
parent fc588c5dbe
commit d87d8bace4
14 changed files with 325 additions and 138 deletions

View File

@ -25,7 +25,8 @@ import {DiagramManager} from "./diagram/diagramManager";
import {Toolbox} from "./toolbox/toolbox"; import {Toolbox} from "./toolbox/toolbox";
import {DualshockEventMapper} from "./util/dualshockEventMapper"; import {DualshockEventMapper} from "./util/dualshockEventMapper";
import log from "loglevel"; import log from "loglevel";
import {AppConfig} from "./util/appConfig";
import {IndexdbPersistenceManager} from "./diagram/indexdbPersistenceManager";
export class App { export class App {
//preTasks = [havokModule]; //preTasks = [havokModule];
@ -35,6 +36,8 @@ export class App {
private rig: Rigplatform; private rig: Rigplatform;
constructor() { constructor() {
const config = AppConfig.config;
log.setLevel('debug'); log.setLevel('debug');
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.style.width = "100%"; canvas.style.width = "100%";
@ -56,10 +59,6 @@ export class App {
this.scene.dispose(); this.scene.dispose();
this.scene = null; this.scene = null;
} }
if (DiagramManager.onDiagramEventObservable) {
DiagramManager.onDiagramEventObservable.clear();
DiagramManager.onDiagramEventObservable = null;
}
const engine = new Engine(canvas, true); const engine = new Engine(canvas, true);
const scene = new Scene(engine); const scene = new Scene(engine);
@ -106,10 +105,14 @@ export class App {
} }
}); });
const persistenceManager = new IndexdbPersistenceManager("diagram");
const diagramManager = new DiagramManager(this.scene, this.xr.baseExperience); const diagramManager = new DiagramManager(this.scene, this.xr.baseExperience);
this.rig = new Rigplatform(this.scene, this.xr); diagramManager.setPersistenceManager(persistenceManager);
const toolbox = new Toolbox(scene, this.xr.baseExperience); AppConfig.config.setPersistenceManager(persistenceManager);
this.rig = new Rigplatform(this.scene, this.xr, diagramManager);
const toolbox = new Toolbox(scene, this.xr.baseExperience, diagramManager);
this.scene.gamepadManager.onGamepadConnectedObservable.add((gamepad) => { this.scene.gamepadManager.onGamepadConnectedObservable.add((gamepad) => {
try { try {
@ -119,7 +122,7 @@ export class App {
const buttonEvent = DualshockEventMapper.mapButtonEvent(button, 1); const buttonEvent = DualshockEventMapper.mapButtonEvent(button, 1);
if (buttonEvent.objectName) { if (buttonEvent.objectName) {
window.dispatchEvent(new CustomEvent('pa-button-state-change', { window.dispatchEvent(new CustomEvent('pa-button-state-change', {
detail: buttonEvent detail: buttonEvent
} }
)); ));
} }
@ -187,7 +190,7 @@ export class App {
} }
}); });
log.info('App', 'keydown event listener added, use Ctrl+Shift+Alt+I to toggle debug layer'); log.info('App', 'keydown event listener added, use Ctrl+Shift+Alt+I to toggle debug layer');
persistenceManager.initialize();
engine.runRenderLoop(() => { engine.runRenderLoop(() => {
scene.render(); scene.render();

View File

@ -1,6 +1,5 @@
import { import {
AbstractMesh, AbstractMesh,
Angle,
InstancedMesh, InstancedMesh,
Mesh, Mesh,
Scene, Scene,
@ -14,7 +13,6 @@ import {DiagramManager} from "../diagram/diagramManager";
import {DiagramEvent, DiagramEventType} from "../diagram/diagramEntity"; import {DiagramEvent, DiagramEventType} from "../diagram/diagramEntity";
import log from "loglevel"; import log from "loglevel";
import {AppConfig} from "../util/appConfig"; import {AppConfig} from "../util/appConfig";
import round from "round";
export class Base { export class Base {
@ -29,12 +27,16 @@ export class Base {
protected previousPosition: Vector3 = null; protected previousPosition: Vector3 = null;
protected readonly xr: WebXRDefaultExperience; protected readonly xr: WebXRDefaultExperience;
protected readonly diagramManager: DiagramManager;
constructor(controller: WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience) { constructor(controller: WebXRInputSource,
scene: Scene,
xr: WebXRDefaultExperience,
diagramManager: DiagramManager) {
this.controller = controller; this.controller = controller;
this.scene = scene; this.scene = scene;
this.xr = xr; this.xr = xr;
this.diagramManager = diagramManager;
this.controller.onMotionControllerInitObservable.add((init) => { this.controller.onMotionControllerInitObservable.add((init) => {
if (init.components['xr-standard-trigger']) { if (init.components['xr-standard-trigger']) {
init.components['xr-standard-trigger'] init.components['xr-standard-trigger']
@ -66,35 +68,6 @@ export class Base {
} }
static snapRotation(rotation): Vector3 {
const config = AppConfig.config;
if (config.rotateSnap == 0) {
return rotation;
}
rotation.x = this.CalcToSnap(rotation.x, config.rotateSnap);
rotation.y = this.CalcToSnap(rotation.y, config.rotateSnap);
rotation.z = this.CalcToSnap(rotation.z, config.rotateSnap);
return rotation;
}
static snapPosition(position): Vector3 {
const config = AppConfig.config;
if (config.gridSnap == 0) {
return position;
}
position.x = round(position.x, config.gridSnap);
position.y = round(position.y, config.gridSnap);
position.z = round(position.z, config.gridSnap);
return position;
}
static CalcToSnap(val, snap) {
const deg = Angle.FromRadians(val).degrees();
const snappedDegrees = round(deg, snap);
log.getLogger('Base').debug("deg", val, deg, snappedDegrees, snap);
return Angle.FromDegrees(snappedDegrees).radians();
}
private initGrip(grip: WebXRControllerComponent) { private initGrip(grip: WebXRControllerComponent) {
grip.onButtonStateChangedObservable.add(() => { grip.onButtonStateChangedObservable.add(() => {
if (grip.changes.pressed) { if (grip.changes.pressed) {
@ -124,10 +97,11 @@ export class Base {
mesh && mesh.setParent(this.controller.motionController.rootMesh); mesh && mesh.setParent(this.controller.motionController.rootMesh);
this.grabbedMesh = mesh; this.grabbedMesh = mesh;
} else { } else {
const config = AppConfig.config;
const newMesh = this.createCopy(mesh); const newMesh = this.createCopy(mesh);
newMesh.position = mesh.absolutePosition.clone(); newMesh.position = mesh.absolutePosition.clone();
newMesh.rotation = mesh.absoluteRotationQuaternion.toEulerAngles().clone(); newMesh.rotation = mesh.absoluteRotationQuaternion.toEulerAngles().clone();
newMesh.scaling = mesh.absoluteScaling.clone(); newMesh.scaling = config.createSnapVal;
newMesh.material = mesh.material; newMesh.material = mesh.material;
newMesh.metadata = mesh.metadata; newMesh.metadata = mesh.metadata;
newMesh && newMesh.setParent(this.controller.motionController.rootMesh); newMesh && newMesh.setParent(this.controller.motionController.rootMesh);
@ -156,15 +130,19 @@ export class Base {
return; return;
} }
} }
const snappedRotation = Base.snapRotation(mesh.absoluteRotationQuaternion.toEulerAngles().clone()); const config = AppConfig.config;
const snappedPosition = Base.snapPosition(mesh.absolutePosition.clone()); const snappedRotation = config.snapRotateVal(mesh.absoluteRotationQuaternion.toEulerAngles().clone());
const snappedPosition = config.snapGridVal(mesh.absolutePosition.clone());
if (this.previousParent) { if (this.previousParent) {
const p = this.scene.getMeshById(this.previousParent); const p = this.scene.getMeshById(this.previousParent);
if (p) { if (p) {
mesh && mesh.setParent(this.scene.getMeshById(this.previousParent)); mesh && mesh.setParent(this.scene.getMeshById(this.previousParent));
log.getLogger("Base").warn("Base", "Have not implemented snapping to parent yet");
//@note: this is not implemented yet
} else { } else {
mesh && mesh.setParent(null); mesh && mesh.setParent(null);
mesh.rotation = snappedRotation;
mesh.position = snappedPosition;
} }
} else { } else {
mesh && mesh.setParent(null); mesh && mesh.setParent(null);
@ -180,7 +158,7 @@ export class Base {
this.previousScaling = null; this.previousScaling = null;
this.previousRotation = null; this.previousRotation = null;
this.previousPosition = null; this.previousPosition = null;
DiagramManager.onDiagramEventObservable.notifyObservers(event); this.diagramManager.onDiagramEventObservable.notifyObservers(event);
} }
} }
}); });

View File

@ -1,7 +1,8 @@
import {AbstractMesh, Observable, TransformNode} from "@babylonjs/core"; import {AbstractMesh, Observable, TransformNode} from "@babylonjs/core";
export type ControllerEventType = { export type ControllerEventType = {
type: string type: string,
value?: number
} }
export class Controllers { export class Controllers {
public static movable: TransformNode | AbstractMesh; public static movable: TransformNode | AbstractMesh;

View File

@ -3,15 +3,17 @@ import {Base} from "./base";
import {Controllers} from "./controllers"; import {Controllers} from "./controllers";
import log from "loglevel"; import log from "loglevel";
import {ConfigMenu} from "../menus/configMenu"; import {ConfigMenu} from "../menus/configMenu";
import {DiagramManager} from "../diagram/diagramManager";
export class Left extends Base { export class Left extends Base {
public static instance: Left; public static instance: Left;
public configMenu: ConfigMenu; public configMenu: ConfigMenu;
constructor(controller:
WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience) {
super(controller, scene, xr); constructor(controller:
WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager) {
super(controller, scene, xr, diagramManager);
this.configMenu = new ConfigMenu(this.scene, xr.baseExperience); this.configMenu = new ConfigMenu(this.scene, xr.baseExperience);
Left.instance = this; Left.instance = this;
this.controller.onMotionControllerInitObservable.add((init) => { this.controller.onMotionControllerInitObservable.add((init) => {

View File

@ -2,13 +2,15 @@ import {Base} from "./base";
import {Scene, Vector3, WebXRControllerComponent, WebXRDefaultExperience, WebXRInputSource} from "@babylonjs/core"; import {Scene, Vector3, WebXRControllerComponent, WebXRDefaultExperience, WebXRInputSource} from "@babylonjs/core";
import {Controllers} from "./controllers"; import {Controllers} from "./controllers";
import log from "loglevel"; import log from "loglevel";
import {DiagramManager} from "../diagram/diagramManager";
export class Right extends Base { export class Right extends Base {
public static instance: Right; public static instance: Right;
constructor(controller: constructor(controller:
WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience) { WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager
super(controller, scene, xr); ) {
super(controller, scene, xr, diagramManager);
Right.instance = this; Right.instance = this;
this.controller.onMotionControllerInitObservable.add((init) => { this.controller.onMotionControllerInitObservable.add((init) => {
this.initTrigger(init.components['xr-standard-trigger']); this.initTrigger(init.components['xr-standard-trigger']);

View File

@ -19,6 +19,7 @@ import {Left} from "./left";
import {EditMenu} from "../menus/editMenu"; import {EditMenu} from "../menus/editMenu";
import {Controllers} from "./controllers"; import {Controllers} from "./controllers";
import log from "loglevel"; import log from "loglevel";
import {DiagramManager} from "../diagram/diagramManager";
export class Rigplatform { export class Rigplatform {
@ -34,13 +35,15 @@ export class Rigplatform {
private camera: Camera; private camera: Camera;
private turning: boolean = false; private turning: boolean = false;
private velocity: Vector3 = Vector3.Zero(); private velocity: Vector3 = Vector3.Zero();
private readonly diagramManager: DiagramManager;
constructor(scene: Scene, xr: WebXRDefaultExperience) { constructor(scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager) {
this.scene = scene; this.scene = scene;
this.diagramManager = diagramManager;
Rigplatform.xr = xr; Rigplatform.xr = xr;
Rigplatform.instance = this; Rigplatform.instance = this;
this.bMenu = new EditMenu(scene, xr.baseExperience); this.bMenu = new EditMenu(scene, xr.baseExperience, this.diagramManager);
this.camera = scene.activeCamera; this.camera = scene.activeCamera;
this.rigMesh = MeshBuilder.CreateBox("platform", {width: 2, height: .02, depth: 2}, scene); this.rigMesh = MeshBuilder.CreateBox("platform", {width: 2, height: .02, depth: 2}, scene);
@ -111,11 +114,11 @@ export class Rigplatform {
let controller; let controller;
switch (source.inputSource.handedness) { switch (source.inputSource.handedness) {
case "right": case "right":
Right.instance = new Right(source, this.scene, Rigplatform.xr); Right.instance = new Right(source, this.scene, Rigplatform.xr, this.diagramManager);
Controllers.controllerObserver.add((event: { type: string, value: number }) => { Controllers.controllerObserver.add((event: { type: string, value: number }) => {
switch (event.type) { switch (event.type) {
case "increaseVelocity": case "increaseVelocity":
if (this.velocityIndex < this.velocityArray.length -1) { if (this.velocityIndex < this.velocityArray.length - 1) {
this.velocityIndex++; this.velocityIndex++;
} else { } else {
this.velocityIndex = 0; this.velocityIndex = 0;
@ -150,7 +153,7 @@ export class Rigplatform {
}); });
break; break;
case "left": case "left":
Left.instance = new Left(source, this.scene, Rigplatform.xr); Left.instance = new Left(source, this.scene, Rigplatform.xr, this.diagramManager);
break; break;
} }

View File

@ -1,41 +1,42 @@
import {Observable, Scene, WebXRExperienceHelper} from "@babylonjs/core"; import {Observable, Scene, WebXRExperienceHelper} from "@babylonjs/core";
import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity"; import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity";
import {IPersistenceManager} from "./persistenceManager"; import {IPersistenceManager} from "./persistenceManager";
import {IndexdbPersistenceManager} from "./indexdbPersistenceManager";
import {MeshConverter} from "./meshConverter"; import {MeshConverter} from "./meshConverter";
import log from "loglevel"; import log from "loglevel";
export class DiagramManager { export class DiagramManager {
private persistenceManager: IPersistenceManager = new IndexdbPersistenceManager("diagram"); public readonly onDiagramEventObservable: Observable<DiagramEvent> = new Observable();
static onDiagramEventObservable: Observable<DiagramEvent> = new Observable(); private readonly logger = log.getLogger('DiagramManager');
private persistenceManager: IPersistenceManager = null;
private readonly scene: Scene; private readonly scene: Scene;
private xr: WebXRExperienceHelper; private xr: WebXRExperienceHelper;
constructor(scene: Scene, xr: WebXRExperienceHelper) { constructor(scene: Scene, xr: WebXRExperienceHelper) {
this.scene = scene; this.scene = scene;
this.xr = xr; this.xr = xr;
this.persistenceManager.updateObserver.add(this.onRemoteEvent, -1, true, this); if (this.onDiagramEventObservable.hasObservers()) {
log.getLogger('DiagramManager').debug( "remote event observer added"); this.logger.warn("onDiagramEventObservable already has Observers, you should be careful");
this.persistenceManager.initialize();
if (!DiagramManager.onDiagramEventObservable) {
log.getLogger('DiagramManager').debug( "onDiagramEventObservable missing, recreated");
DiagramManager.onDiagramEventObservable = new Observable();
} }
if (DiagramManager.onDiagramEventObservable.hasObservers()) { this.onDiagramEventObservable.add(this.onDiagramEvent, -1, true, this);
log.getLogger('DiagramManager').debug("onDiagramEventObservable already has Observers, this shouldn't happen"); this.logger.debug("DiagramManager constructed");
} else {
DiagramManager.onDiagramEventObservable.add(this.onDiagramEvent, -1, true, this);
log.getLogger('DiagramManager').debug( "onDiagramEventObservable Observer added");
}
log.getLogger('DiagramManager').debug( "DiagramManager constructed");
} }
public setPersistenceManager(persistenceManager: IPersistenceManager) {
this.persistenceManager = persistenceManager;
this.persistenceManager.updateObserver.add(this.onRemoteEvent, -1, true, this);
}
private getPersistenceManager(): IPersistenceManager {
if (!this.persistenceManager) {
this.logger.warn("persistenceManager not set");
return null;
}
return this.persistenceManager;
}
private onRemoteEvent(event: DiagramEntity) { private onRemoteEvent(event: DiagramEntity) {
//const mesh = Toolbox.instance.newMesh(ToolType[Object.entries(ToolType).find(e => e[1] == event.template)[0]], event.id); //const mesh = Toolbox.instance.newMesh(ToolType[Object.entries(ToolType).find(e => e[1] == event.template)[0]], event.id);
log.getLogger('DiagramManager').debug(event); this.logger.debug(event);
const mesh = MeshConverter.fromDiagramEntity(event, this.scene); const mesh = MeshConverter.fromDiagramEntity(event, this.scene);
if (event.parent) { if (event.parent) {
mesh.parent = this.scene.getMeshById(event.parent); mesh.parent = this.scene.getMeshById(event.parent);
@ -43,7 +44,7 @@ export class DiagramManager {
} }
private onDiagramEvent(event: DiagramEvent) { private onDiagramEvent(event: DiagramEvent) {
log.getLogger("DiagramManager").debug(event); this.logger.debug(event.type);
const entity = event.entity; const entity = event.entity;
let mesh; let mesh;
if (entity) { if (entity) {
@ -55,19 +56,19 @@ export class DiagramManager {
case DiagramEventType.DROPPED: case DiagramEventType.DROPPED:
break; break;
case DiagramEventType.DROP: case DiagramEventType.DROP:
this.persistenceManager.add(mesh); this.getPersistenceManager()?.add(mesh)
break; break;
case DiagramEventType.ADD: case DiagramEventType.ADD:
break; break;
case DiagramEventType.MODIFY: case DiagramEventType.MODIFY:
this.persistenceManager.modify(mesh); this.getPersistenceManager()?.modify(mesh)
break; break;
case DiagramEventType.CHANGECOLOR: case DiagramEventType.CHANGECOLOR:
this.persistenceManager.changeColor(event.oldColor, event.newColor); this.getPersistenceManager()?.changeColor(event.oldColor, event.newColor);
break; break;
case DiagramEventType.REMOVE: case DiagramEventType.REMOVE:
if (mesh) { if (mesh) {
this.persistenceManager.remove(mesh); this.getPersistenceManager()?.remove(mesh)
mesh.dispose(); mesh.dispose();
} }
break; break;

View File

@ -6,16 +6,18 @@ import {MeshConverter} from "./meshConverter";
import log from "loglevel"; import log from "loglevel";
export class IndexdbPersistenceManager implements IPersistenceManager { export class IndexdbPersistenceManager implements IPersistenceManager {
private readonly logger = log.getLogger('IndexdbPersistenceManager');
public updateObserver: Observable<DiagramEntity> = new Observable<DiagramEntity>(); public updateObserver: Observable<DiagramEntity> = new Observable<DiagramEntity>();
private db: Dexie; private db: Dexie;
constructor(name: string) { constructor(name: string) {
this.db = new Dexie(name); this.db = new Dexie(name);
this.db.version(1).stores({entities: "id,position,rotation,last_seen,template,text,scale,color"}); this.db.version(1).stores({entities: "id,position,rotation,last_seen,template,text,scale,color"});
log.debug('IndexdbPersistenceManager', "IndexdbPersistenceManager constructed"); this.logger.debug("IndexdbPersistenceManager constructed");
} }
public add(mesh: AbstractMesh) { public add(mesh: AbstractMesh) {
if (!mesh) { if (!mesh) {
log.warn('IndexdbPersistenceManager', "Adding null mesh"); this.logger.error("Adding null mesh, early return");
return; return;
} }
const entity = <any>MeshConverter.toDiagramEntity(mesh); const entity = <any>MeshConverter.toDiagramEntity(mesh);
@ -24,36 +26,54 @@ export class IndexdbPersistenceManager implements IPersistenceManager {
entity.scale = this.vectoxys(mesh.scaling); entity.scale = this.vectoxys(mesh.scaling);
this.db["entities"].add(entity); this.db["entities"].add(entity);
} this.logger.debug('add', mesh, entity);
private vectoxys(v: Vector3): {x, y ,z} {
return {x: v.x, y: v.y, z: v.z};
}
private xyztovec(xyz: {x, y, z}): Vector3 {
return new Vector3(xyz.x, xyz.y, xyz.z);
} }
public remove(mesh: AbstractMesh) { public remove(mesh: AbstractMesh) {
this.db["entities"].delete(mesh.id); this.db["entities"].delete(mesh.id);
} }
public modify(mesh) {
public getConfig(): any {
this.logger.warn('getConfig not implemented');
//@todo implement
}
public setConfig(config: any) {
this.logger.warn('setConfig not implemented, value not persisted', config);
//@todo implement
}
public modify(mesh) {
const entity = <any>MeshConverter.toDiagramEntity(mesh); const entity = <any>MeshConverter.toDiagramEntity(mesh);
entity.position = this.vectoxys(mesh.position); entity.position = this.vectoxys(mesh.position);
entity.rotation = this.vectoxys(mesh.rotation); entity.rotation = this.vectoxys(mesh.rotation);
entity.scale = this.vectoxys(mesh.scaling); entity.scale = this.vectoxys(mesh.scaling);
this.db["entities"].update(mesh.id, entity); this.db["entities"].update(mesh.id, entity);
this.logger.debug('modify', mesh, entity);
} }
public initialize() { public initialize() {
this.logger.info('initialize', this.db['entities'].length);
this.db['entities'].each((e) => { this.db['entities'].each((e) => {
e.position = this.xyztovec(e.position); e.position = this.xyztovec(e.position);
e.rotation = this.xyztovec(e.rotation); e.rotation = this.xyztovec(e.rotation);
e.scale = this.xyztovec(e.scale); e.scale = this.xyztovec(e.scale);
log.debug('IndexdbPersistenceManager', 'adding', e); this.logger.debug('adding', e);
this.updateObserver.notifyObservers(e); this.updateObserver.notifyObservers(e);
}); });
log.warn('IndexdbPersistenceManager', "initialize finished"); this.logger.info("initialize finished");
} }
public changeColor(oldColor, newColor) { public changeColor(oldColor, newColor) {
log.debug('IndexdbPersistenceManager', `changeColor ${oldColor.toHexString()} to ${newColor.toHexString()}`); this.logger.debug(`changeColor ${oldColor.toHexString()} to ${newColor.toHexString()}`);
this.db['entities'].where('color').equals(oldColor.toHexString()).modify({color: newColor.toHexString()}); this.db['entities'].where('color').equals(oldColor.toHexString()).modify({color: newColor.toHexString()});
} }
private vectoxys(v: Vector3): { x, y, z } {
return {x: v.x, y: v.y, z: v.z};
}
private xyztovec(xyz: { x, y, z }): Vector3 {
return new Vector3(xyz.x, xyz.y, xyz.z);
}
} }

View File

@ -1,12 +1,22 @@
import {AbstractMesh, Color3, Observable} from "@babylonjs/core"; import {AbstractMesh, Color3, Observable} from "@babylonjs/core";
import {DiagramEntity} from "./diagramEntity"; import {DiagramEntity} from "./diagramEntity";
import {AppConfigType} from "../util/appConfig";
export interface IPersistenceManager { export interface IPersistenceManager {
add(mesh: AbstractMesh); add(mesh: AbstractMesh);
remove(mesh: AbstractMesh); remove(mesh: AbstractMesh);
modify(mesh: AbstractMesh); modify(mesh: AbstractMesh);
initialize(); initialize();
getConfig(): AppConfigType;
setConfig(config: AppConfigType);
changeColor(oldColor: Color3, newColor: Color3) changeColor(oldColor: Color3, newColor: Color3)
updateObserver: Observable<DiagramEntity>; updateObserver: Observable<DiagramEntity>;
} }

View File

@ -3,7 +3,7 @@ import {AdvancedDynamicTexture, InputText} from "@babylonjs/gui";
export class InputTextView { export class InputTextView {
private mesh: AbstractMesh; private mesh: AbstractMesh;
private scene: Scene; private readonly scene: Scene;
private xr: WebXRExperienceHelper; private xr: WebXRExperienceHelper;
private inputPlane: AbstractMesh; private inputPlane: AbstractMesh;
private inputText: InputText; private inputText: InputText;

View File

@ -38,30 +38,52 @@ export class ConfigMenu {
selectionPanel.fontSize = "24px"; selectionPanel.fontSize = "24px";
selectionPanel.height = "100%"; selectionPanel.height = "100%";
configTexture.addControl(selectionPanel) configTexture.addControl(selectionPanel)
const radio1 = new RadioGroup("Rotation Snap"); selectionPanel.addGroup(this.buildGridSizeControl());
radio1.addRadio("Off", this.rotateVal); selectionPanel.addGroup(this.buildRotationSnapControl());
radio1.addRadio("22.5 degrees", this.rotateVal); selectionPanel.addGroup(this.buildCreateScaleControl());
radio1.addRadio("45 degrees", this.rotateVal);
radio1.addRadio("90 degrees", this.rotateVal);
selectionPanel.addGroup(radio1);
const radio2 = new RadioGroup("Grid Snap");
radio2.addRadio("Off", this.gridVal);
radio2.addRadio("1 cm", this.gridVal);
radio2.addRadio("10 cm", this.gridVal);
radio2.addRadio("25 cm", this.gridVal);
selectionPanel.addGroup(radio1);
selectionPanel.addGroup(radio2);
this.configPlane.position = CameraHelper.getFrontPosition(2, this.scene); this.configPlane.position = CameraHelper.getFrontPosition(2, this.scene);
this.configPlane.rotation.y = Angle.FromDegrees(180).radians(); this.configPlane.rotation.y = Angle.FromDegrees(180).radians();
} }
private createVal(value) {
AppConfig.config.currentCreateSnapIndex = value;
log.debug("configMenu", "create Snap", value);
}
private buildCreateScaleControl(): RadioGroup {
const radio = new RadioGroup("Create Scale");
for (const [index, snap] of AppConfig.config.createSnaps().entries()) {
const selected = AppConfig.config.currentCreateSnapIndex == index;
radio.addRadio(snap.label, this.createVal, selected);
}
return radio;
}
private buildRotationSnapControl(): RadioGroup {
const radio = new RadioGroup("Rotation Snap");
for (const [index, snap] of AppConfig.config.rotateSnaps().entries()) {
const selected = AppConfig.config.currentRotateSnapIndex == index;
radio.addRadio(snap.label, this.rotateVal, selected);
}
return radio;
}
private buildGridSizeControl(): RadioGroup {
const radio = new RadioGroup("Grid Snap");
for (const [index, snap] of AppConfig.config.gridSnaps().entries()) {
const selected = AppConfig.config.currentGridSnapIndex == index;
radio.addRadio(snap.label, this.gridVal, selected);
}
return radio;
}
private rotateVal(value) { private rotateVal(value) {
AppConfig.config.rotateSnap = AppConfig.config.rotateSnapArray[value]; AppConfig.config.currentRotateSnapIndex = value;
log.debug("configMenu", "rotate Snap", value); log.debug("configMenu", "rotate Snap", value);
} }
private gridVal(value) { private gridVal(value) {
AppConfig.config.gridSnap = AppConfig.config.gridSnapArray[value]; AppConfig.config.currentGridSnapIndex = value;
log.debug("configMenu", "grid Snap", value); log.debug("configMenu", "grid Snap", value);
} }

View File

@ -24,12 +24,13 @@ export class EditMenu {
private textView: InputTextView; private textView: InputTextView;
private textInput: HTMLElement; private textInput: HTMLElement;
private gizmoManager: GizmoManager; private gizmoManager: GizmoManager;
private xr: WebXRExperienceHelper; private readonly xr: WebXRExperienceHelper;
private readonly diagramManager: DiagramManager;
constructor(scene: Scene, xr: WebXRExperienceHelper) {
constructor(scene: Scene, xr: WebXRExperienceHelper, diagramManager: DiagramManager) {
this.scene = scene; this.scene = scene;
this.xr = xr; this.xr = xr;
this.diagramManager = diagramManager;
this.gizmoManager = new GizmoManager(scene); this.gizmoManager = new GizmoManager(scene);
this.gizmoManager.boundingBoxGizmoEnabled = true; this.gizmoManager.boundingBoxGizmoEnabled = true;
this.gizmoManager.gizmos.boundingBoxGizmo.scaleBoxSize = .020; this.gizmoManager.gizmos.boundingBoxGizmo.scaleBoxSize = .020;
@ -71,7 +72,7 @@ export class EditMenu {
entity: entity:
MeshConverter.toDiagramEntity(pointerInfo.pickInfo.pickedMesh) MeshConverter.toDiagramEntity(pointerInfo.pickInfo.pickedMesh)
} }
DiagramManager.onDiagramEventObservable.notifyObservers(event); this.diagramManager.onDiagramEventObservable.notifyObservers(event);
break; break;
case BmenuState.MODIFYING: case BmenuState.MODIFYING:
if (pointerInfo.pickInfo.pickedMesh.metadata?.template && if (pointerInfo.pickInfo.pickedMesh.metadata?.template &&
@ -83,7 +84,7 @@ export class EditMenu {
this.gizmoManager.attachToMesh(mesh); this.gizmoManager.attachToMesh(mesh);
this.gizmoManager.gizmos.boundingBoxGizmo.onScaleBoxDragObservable.add(() => { this.gizmoManager.gizmos.boundingBoxGizmo.onScaleBoxDragObservable.add(() => {
DiagramManager.onDiagramEventObservable.notifyObservers({ this.diagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.MODIFY, type: DiagramEventType.MODIFY,
entity: MeshConverter.toDiagramEntity(mesh), entity: MeshConverter.toDiagramEntity(mesh),
} }
@ -158,7 +159,7 @@ export class EditMenu {
} else { } else {
log.getLogger('bmenu').error("mesh has no metadata"); log.getLogger('bmenu').error("mesh has no metadata");
} }
DiagramManager.onDiagramEventObservable.notifyObservers({ this.diagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.MODIFY, type: DiagramEventType.MODIFY,
entity: MeshConverter.toDiagramEntity(mesh), entity: MeshConverter.toDiagramEntity(mesh),
}); });

View File

@ -27,27 +27,30 @@ export enum ToolType {
} }
export class Toolbox { export class Toolbox {
public static getToolTypeFromString(type: string): ToolType {
return ToolType[Object.keys(ToolType).find(() => type)]
}
private index = 0; private index = 0;
public static instance: Toolbox; public static instance: Toolbox;
private readonly scene: Scene; private readonly scene: Scene;
private readonly xr: WebXRExperienceHelper; private readonly xr: WebXRExperienceHelper;
public readonly node : TransformNode; public readonly node: TransformNode;
private readonly diagramManager: DiagramManager;
private readonly manager: GUI3DManager; private readonly manager: GUI3DManager;
private readonly gridsize = 5; private readonly gridsize = 5;
private readonly addPanel: StackPanel3D; private readonly addPanel: StackPanel3D;
constructor (scene:Scene, xr: WebXRExperienceHelper) {
constructor(scene: Scene, xr: WebXRExperienceHelper, diagramManager: DiagramManager) {
this.scene = scene; this.scene = scene;
this.diagramManager = diagramManager;
this.addPanel = new StackPanel3D(); this.addPanel = new StackPanel3D();
this.manager = new GUI3DManager(scene); this.manager = new GUI3DManager(scene);
this.manager.addControl(this.addPanel); this.manager.addControl(this.addPanel);
this.node = new TransformNode("toolbox", this.scene); this.node = new TransformNode("toolbox", this.scene);
const handle = MeshBuilder.CreateCapsule("handle", { radius: .01 , orientation: Vector3.Right(), height: .3}, this.scene); const handle = MeshBuilder.CreateCapsule("handle", {
radius: .01,
orientation: Vector3.Right(),
height: .3
}, this.scene);
handle.id = "handle"; handle.id = "handle";
const handleMaterial = new StandardMaterial("handle-material", this.scene); const handleMaterial = new StandardMaterial("handle-material", this.scene);
handleMaterial.diffuseColor = Color3.FromHexString("#EEEEFF"); handleMaterial.diffuseColor = Color3.FromHexString("#EEEEFF");
handle.material = handleMaterial; handle.material = handleMaterial;
handle.position = CameraHelper.getFrontPosition(2, this.scene); handle.position = CameraHelper.getFrontPosition(2, this.scene);
@ -129,7 +132,7 @@ export class Toolbox {
material.name = "material-" + value.toHexString(); material.name = "material-" + value.toHexString();
mesh.id = "toolbox-color-" + value.toHexString(); mesh.id = "toolbox-color-" + value.toHexString();
mesh.name = "toolbox-color-" + value.toHexString(); mesh.name = "toolbox-color-" + value.toHexString();
DiagramManager.onDiagramEventObservable.notifyObservers( this.diagramManager.onDiagramEventObservable.notifyObservers(
{ {
type: DiagramEventType.CHANGECOLOR, type: DiagramEventType.CHANGECOLOR,
oldColor: oldColor, oldColor: oldColor,
@ -206,9 +209,6 @@ export class Toolbox {
return null; return null;
} }
} }
public show() {
this.buildToolbox();
}
} }
function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] { function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[]; return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[];

View File

@ -1,10 +1,45 @@
import {Angle, Vector3} from "@babylonjs/core";
import round from "round";
import log from "loglevel";
import {IPersistenceManager} from "../diagram/persistenceManager";
export type SnapValue = {
value: number,
label: string
}
export type AppConfigType = {
gridSnap: number,
rotateSnap: number,
createSnap: number
}
export class AppConfig { export class AppConfig {
public gridSnap = 0; private gridSnap = 0;
public rotateSnap = 0; private rotateSnap = 0;
public gridSnapArray = private createSnap = 0;
[0, 0.1, 0.5, 1]; private readonly defaultGridSnapIndex = 1;
public rotateSnapArray = private persistenceManager: IPersistenceManager = null;
[0, 22.5, 45, 90] private gridSnapArray: SnapValue[] =
[{value: 0, label: "Off"},
{value: 0.05, label: "(Default)"},
{value: 0.01, label: "1 cm"},
{value: 0.1, label: "10 cm"},
{value: 0.25, label: "25 cm"},
{value: .5, label: ".5 m"}]
private createSnapArray: SnapValue[] =
[{value: .1, label: "Default (10 cm)"},
{value: 0.2, label: "20 cm"},
{value: 0.5, label: ".5 m"},
{value: 1, label: "1 m"}];
private rotateSnapArray: SnapValue[] =
[{value: 0, label: "Off"},
{value: 22.5, label: "22.5 Degrees"},
{value: 45, label: "45 Degrees"},
{value: 90, label: "90 Degrees"}];
public get currentGridSnap(): SnapValue {
return this.gridSnapArray[this.gridSnap];
}
private static _config: AppConfig; private static _config: AppConfig;
@ -13,7 +48,116 @@ export class AppConfig {
AppConfig._config = new AppConfig(); AppConfig._config = new AppConfig();
} }
return AppConfig._config; return AppConfig._config;
} }
public get currentRotateSnap(): SnapValue {
return this.rotateSnapArray[this.rotateSnap];
}
public get currentCreateSnap(): SnapValue {
return this.createSnapArray[this.createSnap];
}
public get currentGridSnapIndex(): number {
return this.gridSnap;
}
public set currentGridSnapIndex(val: number) {
this.gridSnap = val;
this.save();
}
public get currentCreateSnapIndex(): number {
return this.createSnap;
}
public set currentCreateSnapIndex(val: number) {
this.createSnap = val;
if (this.currentGridSnapIndex == this.defaultGridSnapIndex) {
this.currentGridSnap.value = this.currentCreateSnap.value / 2;
log.getLogger('AppConfig').debug("Set grid snap to " + this.currentGridSnap.value);
}
this.save();
}
public get currentRotateSnapIndex(): number {
return this.rotateSnap;
}
public set currentRotateSnapIndex(val: number) {
this.rotateSnap = val;
this.save();
}
public get createSnapVal(): Vector3 {
return new Vector3(this.currentCreateSnap.value, this.currentCreateSnap.value, this.currentCreateSnap.value);
}
public setPersistenceManager(persistenceManager: IPersistenceManager) {
this.persistenceManager = persistenceManager;
this.load();
}
public gridSnaps(): SnapValue[] {
return this.gridSnapArray;
}
public createSnaps(): SnapValue[] {
return this.createSnapArray;
}
public rotateSnaps(): SnapValue[] {
return this.rotateSnapArray;
}
public snapGridVal(value: Vector3): Vector3 {
if (this.currentGridSnapIndex == 0) {
return value;
}
const position = value.clone();
position.x = round(position.x, this.currentGridSnap.value);
position.y = round(position.y, this.currentGridSnap.value);
position.z = round(position.z, this.currentGridSnap.value);
return position;
}
public snapRotateVal(value: Vector3): Vector3 {
if (this.currentRotateSnapIndex == 0) {
return value;
}
const rotation = new Vector3();
rotation.x = this.snapAngle(value.x);
rotation.y = this.snapAngle(value.y);
rotation.z = this.snapAngle(value.z);
return rotation;
}
private save() {
this.persistenceManager.setConfig(
{
gridSnap: this.currentGridSnap.value,
rotateSnap: this.currentRotateSnap.value,
createSnap: this.currentCreateSnap.value
});
}
private load() {
const config = this.persistenceManager.getConfig();
if (config) {
this.rotateSnap = this.rotateSnapArray.findIndex((snap) => snap.value == config.rotateSnap);
this.createSnap = this.createSnapArray.findIndex((snap) => snap.value == config.createSnap);
const gridSnap = this.gridSnapArray.findIndex((snap) => snap.value == config.gridSnap);
if (gridSnap == -1) {
this.gridSnap = this.defaultGridSnapIndex;
this.currentGridSnap.value = config.gridSnap;
}
}
}
private snapAngle(val: number): number {
const deg = Angle.FromRadians(val).degrees();
const snappedDegrees = round(deg, this.currentRotateSnap.value);
log.getLogger('AppConfig').debug("deg", val, deg, snappedDegrees, this.currentRotateSnap.value);
return Angle.FromDegrees(snappedDegrees).radians();
}
} }