diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8eed753..0cff3e8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,5 +16,4 @@ jobs: uses: actions/setup-node@v2 with: node-version: 20.x - - run: echo "test" - run: cp -r ./dist/* /var/www/deepdiagram diff --git a/src/controllers/base.ts b/src/controllers/base.ts index 6e57baa..c51cae7 100644 --- a/src/controllers/base.ts +++ b/src/controllers/base.ts @@ -1,8 +1,6 @@ import { AbstractMesh, - PhysicsMotionType, Scene, - TransformNode, Vector3, WebXRControllerComponent, WebXRDefaultExperience, @@ -12,45 +10,38 @@ import {DiagramManager} from "../diagram/diagramManager"; import {DiagramEvent, DiagramEventType} from "../diagram/types/diagramEntity"; import log from "loglevel"; import {ControllerEventType, Controllers} from "./controllers"; -import {toDiagramEntity} from "../diagram/functions/toDiagramEntity"; -import {setupTransformNode} from "./functions/setupTransformNode"; -import {reparent} from "./functions/reparent"; -import {snapGridVal} from "../util/functions/snapGridVal"; -import {snapRotateVal} from "../util/functions/snapRotateVal"; import {grabAndClone} from "./functions/grabAndClone"; -import {isDiagramEntity} from "../diagram/functions/isDiagramEntity"; import {ClickMenu} from "../menus/clickMenu"; import {displayDebug} from "../util/displayDebug"; -import {beforeRenderObserver} from "./functions/beforeRenderObserver"; import {motionControllerObserver} from "./functions/motionControllerObserver"; -import {handleWasGrabbed} from "./functions/handleWasGrabbed"; -import {buildDrop} from "./functions/buildDrop"; -import {pointable} from "./functions/pointable"; import {DefaultScene} from "../defaultScene"; import {DiagramEventObserverMask} from "../diagram/types/diagramEventObserverMask"; +import {DiagramObject} from "../objects/diagramObject"; +import {snapAll} from "./functions/snapAll"; +import {MeshTypeEnum} from "../diagram/types/meshTypeEnum"; +import {getMeshType} from "./functions/getMeshType"; const CLICK_TIME = 300; + export class Base { static stickVector = Vector3.Zero(); protected xrInputSource: WebXRInputSource; protected speedFactor = 4; protected readonly scene: Scene; + protected grabbedObject: DiagramObject = null; protected grabbedMesh: AbstractMesh = null; - protected grabbedMeshParentId: string = null; - protected previousParentId: string = null; - protected previousRotation: Vector3 = null; - protected previousScaling: Vector3 = null; - protected previousPosition: Vector3 = null; + protected grabbedMeshType: MeshTypeEnum = null; private clickStart: number = 0; protected readonly xr: WebXRDefaultExperience; protected readonly diagramManager: DiagramManager; - private lastPosition: Vector3 = null; protected controllers: Controllers; private clickMenu: ClickMenu; private pickPoint: Vector3 = new Vector3(); + private meshUnderPointer: AbstractMesh; private logger = log.getLogger('Base'); + constructor(controller: WebXRInputSource, xr: WebXRDefaultExperience, diagramManager: DiagramManager) { @@ -60,15 +51,14 @@ export class Base { this.scene = DefaultScene.Scene; this.xr = xr; this.scene.onPointerObservable.add((pointerInfo) => { - if (pointerInfo.pickInfo.pickedMesh?.metadata?.template) { - const mesh = pointerInfo.pickInfo.pickedMesh; - //const pos = mesh.absolutePosition; - this.pickPoint.copyFrom(pointerInfo.pickInfo.pickedPoint); + if (pointerInfo.pickInfo.pickedMesh) { + this.meshUnderPointer = pointerInfo.pickInfo.pickedMesh; + if (this.diagramManager.getDiagramObject(pointerInfo.pickInfo.pickedMesh.id)) { + this.pickPoint.copyFrom(pointerInfo.pickInfo.pickedPoint); + } } - }); this.diagramManager = diagramManager; - this.scene.onBeforeRenderObservable.add(beforeRenderObserver, -1, false, this); //@TODO THis works, but it uses initGrip, not sure if this is the best idea this.xrInputSource.onMotionControllerInitObservable.add(motionControllerObserver, -1, false, this); @@ -77,7 +67,7 @@ export class Base { switch (event.type) { case ControllerEventType.PULSE: if (event.gripId == this?.xrInputSource?.grip?.id) { - this.xrInputSource?.motionController?.pulse(.25, 30) + this.xrInputSource?.motionController?.pulse(.35, 50) .then(() => { this.logger.debug("pulse done"); }); @@ -115,7 +105,9 @@ export class Base { window.setTimeout(() => { if (this.clickStart > 0) { this.logger.debug("grabbing and cloning"); - this.grab(true); + const clone = grabAndClone(this.diagramManager, this.meshUnderPointer, this.xrInputSource.motionController.rootMesh); + this.grabbedObject = clone; + this.grabbedMesh = clone.mesh; } }, 300, this); } @@ -123,67 +115,41 @@ export class Base { const clickEnd = Date.now(); if (this.clickStart > 0 && (clickEnd - this.clickStart) < CLICK_TIME) { this.click(); + } else { + if (this.grabbedObject || this.grabbedMesh) { + this.drop(); + } } - this.drop(); this.clickStart = 0; } } }, -1, false, this); } - private grab(cloneEntity: boolean = false) { - let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId); + private grab() { + let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId); if (!mesh) { return; } - let player = false; + this.grabbedMesh = mesh; + this.grabbedMeshType = getMeshType(mesh, this.diagramManager); + displayDebug(mesh); - if (!isDiagramEntity(mesh)) { - if (handleWasGrabbed(mesh)) { - mesh && mesh.setParent(this.xrInputSource.motionController.rootMesh); - this.grabbedMesh = mesh; - } else { - if (mesh?.metadata?.grabbable) { - this.grabbedMesh = mesh; - } else { - return; - } - } - } else { - if (mesh?.metadata?.template == '#connection-template') { - return; - } - } + switch (this.grabbedMeshType) { + case MeshTypeEnum.ENTITY: + const diagramObject = this.diagramManager.getDiagramObject(mesh.id); + diagramObject.baseTransform.setParent(this.xrInputSource.motionController.rootMesh); + this.grabbedObject = diagramObject; + break; + case MeshTypeEnum.HANDLE: + this.grabbedMesh.setParent(this.xrInputSource.motionController.rootMesh); + break; + case MeshTypeEnum.TOOL: + const clone = grabAndClone(this.diagramManager, mesh, this.xrInputSource.motionController.rootMesh); + this.grabbedObject = clone; + this.grabbedMesh = clone.mesh; - this.previousParentId = mesh?.parent?.id; - this.logger.warn("grabbed " + mesh?.id + " parent " + this.previousParentId); - this.previousRotation = mesh?.rotation.clone(); - this.previousScaling = mesh?.scaling.clone(); - this.previousPosition = mesh?.position.clone(); - - if ((!mesh.metadata?.grabClone || player) && !cloneEntity) { - if (mesh.physicsBody) { - const transformNode = setupTransformNode(mesh, this.xrInputSource.motionController.rootMesh); - mesh.physicsBody.setMotionType(PhysicsMotionType.ANIMATED); - this.grabbedMeshParentId = transformNode.id; - } else { - mesh.setParent(this.xrInputSource.motionController.rootMesh); - } - this.grabbedMesh = mesh; - } else { - this.logger.debug("cloning " + mesh?.id); - const clone = grabAndClone(this.diagramManager, mesh, this.xrInputSource.motionController.rootMesh); - clone.newMesh.metadata.grabClone = false; - clone.newMesh.metadata.tool = false; - this.grabbedMeshParentId = clone.transformNode.id; - this.grabbedMesh = clone.newMesh; - this.previousParentId = null; - const event: DiagramEvent = { - type: DiagramEventType.ADD, - entity: toDiagramEntity(clone.newMesh) - } - this.diagramManager.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL); } } @@ -192,58 +158,58 @@ export class Base { if (!mesh) { return; } - if (handleWasGrabbed(mesh)) { - 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); - this.grabbedMeshParentId = null; - if (!mesh.physicsBody) { - const transform = new TransformNode('temp', this.scene); - transform.position = this.pickPoint; - mesh.setParent(transform); - mesh.rotation = snapRotateVal(mesh.rotation, this.diagramManager._config.current.rotateSnap); - transform.position = snapGridVal(transform.position, this.diagramManager._config.current.gridSnap); - mesh.setParent(null); - mesh.position = snapGridVal(mesh.position, this.diagramManager._config.current.gridSnap); - //mesh.position = snapGridVal(mesh.position, this.diagramManager._config.current.gridSnap); - //mesh.setPivotPoint(transform.position, Space.WORLD) + const diagramObject = this.grabbedObject; + switch (this.grabbedMeshType) { + case MeshTypeEnum.ENTITY: + if (diagramObject) { + diagramObject.baseTransform.setParent(null); + snapAll(this.grabbedObject.baseTransform, this.diagramManager.config, this.pickPoint); + diagramObject.mesh.computeWorldMatrix(true); + const event: DiagramEvent = + { + type: DiagramEventType.DROP, + entity: diagramObject.diagramEntity + } + this.diagramManager.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL); + diagramObject.mesh.computeWorldMatrix(false); + } - - //transform.dispose(); + this.grabbedObject = null; + this.grabbedMesh = null; + this.grabbedMeshType = null; + break; + case MeshTypeEnum.TOOL: + this.grabbedObject.baseTransform.setParent(null); + snapAll(this.grabbedObject.baseTransform, this.diagramManager.config, this.pickPoint); + diagramObject.mesh.computeWorldMatrix(true); + const event: DiagramEvent = + { + type: DiagramEventType.DROP, + entity: diagramObject.diagramEntity + } + this.diagramManager.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL); + diagramObject.mesh.computeWorldMatrix(false); + this.grabbedObject = null; + this.grabbedMesh = null; + this.grabbedMeshType = null; + break; + case MeshTypeEnum.HANDLE: + 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)); + this.grabbedMesh = null; + this.grabbedMeshType = null; + this.grabbedObject = null; + break; } - this.previousParentId = null; - this.previousScaling = null; - this.previousRotation = null; - this.previousPosition = null; - this.grabbedMesh = null; - if (isDiagramEntity(mesh) && (mesh?.metadata?.template.indexOf('#') == -1)) { - return; - } - const event: DiagramEvent = buildDrop(mesh); - - const body = mesh?.physicsBody; - if (body) { - body.setMotionType(PhysicsMotionType.DYNAMIC); - this.logger.debug(body.transformNode.absolutePosition); - this.logger.debug(this.lastPosition); - if (this.lastPosition) { - body.setLinearVelocity(body.transformNode.absolutePosition.subtract(this.lastPosition).scale(20)); - //body.setLinearVelocity(this.lastPosition.subtract(body.transformNode.absolutePosition).scale(20)); - this.logger.debug(this.lastPosition.subtract(body.transformNode.absolutePosition).scale(20)); - } - } - this.diagramManager.onDiagramEventObservable.notifyObservers(event, DiagramEventObserverMask.ALL); } private click() { let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId); - if (pointable(mesh)) { + if (this.diagramManager.isDiagramObject(mesh)) { this.logger.debug("click on " + mesh.id); if (this.clickMenu && !this.clickMenu.isDisposed) { if (this.clickMenu.isConnecting) { @@ -253,12 +219,9 @@ export class Base { } else { this.clickMenu = this.diagramManager.diagramMenuManager.createClickMenu(mesh, this.xrInputSource); } - } else { this.logger.debug("click on nothing"); } - - } private initGrip(grip: WebXRControllerComponent) { @@ -272,4 +235,4 @@ export class Base { } }); } -} +} \ No newline at end of file diff --git a/src/controllers/functions/getMeshType.ts b/src/controllers/functions/getMeshType.ts new file mode 100644 index 0000000..9dab9f6 --- /dev/null +++ b/src/controllers/functions/getMeshType.ts @@ -0,0 +1,18 @@ +import {AbstractMesh} from "@babylonjs/core"; +import {MeshTypeEnum} from "../../diagram/types/meshTypeEnum"; +import {Toolbox} from "../../toolbox/toolbox"; +import {handleWasGrabbed} from "./handleWasGrabbed"; +import {DiagramManager} from "../../diagram/diagramManager"; + +export function getMeshType(mesh: AbstractMesh, diagramManager: DiagramManager): MeshTypeEnum { + if (Toolbox.instance.isTool(mesh)) { + return MeshTypeEnum.TOOL; + } + if (handleWasGrabbed(mesh)) { + return MeshTypeEnum.HANDLE; + } + if (diagramManager.isDiagramObject(mesh)) { + return MeshTypeEnum.ENTITY; + } + return null; +} diff --git a/src/controllers/functions/grabAndClone.ts b/src/controllers/functions/grabAndClone.ts index 39a9194..d15954a 100644 --- a/src/controllers/functions/grabAndClone.ts +++ b/src/controllers/functions/grabAndClone.ts @@ -1,20 +1,33 @@ -import {AbstractMesh, TransformNode} from "@babylonjs/core"; +import {AbstractMesh, Vector3} from "@babylonjs/core"; import {DiagramManager} from "../../diagram/diagramManager"; -import {DefaultScene} from "../../defaultScene"; +import {DiagramObject} from "../../objects/diagramObject"; export function grabAndClone(diagramManager: DiagramManager, mesh: AbstractMesh, parent: AbstractMesh): - { transformNode: TransformNode, newMesh: AbstractMesh } { - const scene = DefaultScene.Scene; - const newMesh = diagramManager.createCopy(mesh, true); - const transformNode = new TransformNode("grabAnchor", scene); - transformNode.id = "grabAnchor"; - transformNode.position = newMesh.position.clone(); - if (newMesh.rotationQuaternion) { - transformNode.rotationQuaternion = newMesh.rotationQuaternion.clone(); + DiagramObject { + if (diagramManager.isDiagramObject(mesh)) { + const diagramObject = diagramManager.createCopy(mesh.id); + if (!diagramObject) { + return null; + } + diagramObject.baseTransform.setParent(parent); + return diagramObject; } else { - transformNode.rotation = newMesh.rotation.clone(); + const entity = { + template: mesh.metadata.template, + color: mesh.metadata.color, + position: vectoxys(mesh.absolutePosition), + rotation: vectoxys(mesh.absoluteRotationQuaternion.toEulerAngles()), + scale: vectoxys(mesh.scaling) + + } + const obj = new DiagramObject(parent.getScene(), {diagramEntity: entity}); + obj.baseTransform.setParent(parent); + return obj; + } - transformNode.setParent(parent); - newMesh.setParent(transformNode); - return {transformNode: transformNode, newMesh: newMesh}; + +} + +function vectoxys(v: Vector3): { x, y, z } { + return {x: v.x, y: v.y, z: v.z}; } \ No newline at end of file diff --git a/src/controllers/functions/motionControllerObserver.ts b/src/controllers/functions/motionControllerObserver.ts index 4a31324..38c4bec 100644 --- a/src/controllers/functions/motionControllerObserver.ts +++ b/src/controllers/functions/motionControllerObserver.ts @@ -10,4 +10,4 @@ export function motionControllerObserver(init) { if (init.components['xr-standard-trigger']) { this.initClicker(init.components['xr-standard-trigger']); } -} \ No newline at end of file +} diff --git a/src/controllers/functions/pointable.ts b/src/controllers/functions/pointable.ts deleted file mode 100644 index 7fba59d..0000000 --- a/src/controllers/functions/pointable.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {AbstractMesh} from "@babylonjs/core"; - -export function pointable(mesh: AbstractMesh): boolean { - return (mesh && mesh.metadata?.template && - !mesh.metadata?.tool && - !mesh.metadata?.handle && - !mesh.metadata?.grabbable && - !mesh.metadata?.grabClone); -} \ No newline at end of file diff --git a/src/controllers/functions/snapAll.ts b/src/controllers/functions/snapAll.ts new file mode 100644 index 0000000..bbd95d2 --- /dev/null +++ b/src/controllers/functions/snapAll.ts @@ -0,0 +1,15 @@ +import {TransformNode, Vector3} from "@babylonjs/core"; +import {AppConfig} from "../../util/appConfig"; +import {snapRotateVal} from "../../util/functions/snapRotateVal"; +import {snapGridVal} from "../../util/functions/snapGridVal"; + +export function snapAll(node: TransformNode, config: AppConfig, pickPoint: Vector3) { + const transform = new TransformNode('temp', node.getScene()); + transform.position = pickPoint; + node.setParent(transform); + node.rotation = snapRotateVal(node.absoluteRotationQuaternion.toEulerAngles(), config.current.rotateSnap); + transform.position = snapGridVal(transform.absolutePosition, config.current.gridSnap); + node.setParent(null); + node.position = snapGridVal(node.absolutePosition, config.current.gridSnap); + transform.dispose(); +} diff --git a/src/controllers/right.ts b/src/controllers/right.ts index 174660b..8dc1791 100644 --- a/src/controllers/right.ts +++ b/src/controllers/right.ts @@ -4,12 +4,9 @@ import {ControllerEventType} from "./controllers"; import {DiagramManager} from "../diagram/diagramManager"; import log from "loglevel"; -import {DefaultScene} from "../defaultScene"; export class Right extends Base { - - private startPosition: Vector3 = null; private rightLogger = log.getLogger("Right"); private initBButton(bbutton: WebXRControllerComponent) { if (bbutton) { @@ -24,18 +21,12 @@ export class Right extends Base { }); } } - - private startTime: number = null; - private endPosition: Vector3 = null; - constructor(controller: WebXRInputSource, xr: WebXRDefaultExperience, diagramManager: DiagramManager ) { super(controller, xr, diagramManager); - const scene = DefaultScene.Scene; - this.xrInputSource.onMotionControllerInitObservable.add((init) => { this.initTrigger(init.components['xr-standard-trigger']); this.initBButton(init.components['b-button']); diff --git a/src/controllers/rigplatform.ts b/src/controllers/rigplatform.ts index 7b011ec..4b4d891 100644 --- a/src/controllers/rigplatform.ts +++ b/src/controllers/rigplatform.ts @@ -184,6 +184,18 @@ export class Rigplatform { } }); this.xr.input.onControllerRemovedObservable.add((source) => { + switch (source.inputSource.handedness) { + case RIGHT: + if (this.rightController) { + this.rightController = null; + } + break; + case LEFT: + if (this.leftController) { + this.leftController = null; + } + break; + } this.logger.debug('controller removed', source); }); } diff --git a/src/controllers/webController.ts b/src/controllers/webController.ts index 6982f14..8016b45 100644 --- a/src/controllers/webController.ts +++ b/src/controllers/webController.ts @@ -6,9 +6,6 @@ import {GridMaterial} from "@babylonjs/materials"; import {wheelHandler} from "./functions/wheelHandler"; import log, {Logger} from "loglevel"; import {isDiagramEntity} from "../diagram/functions/isDiagramEntity"; -import {DiagramEventType} from "../diagram/types/diagramEntity"; -import {toDiagramEntity} from "../diagram/functions/toDiagramEntity"; -import {DiagramEventObserverMask} from "../diagram/types/diagramEventObserverMask"; export class WebController { private readonly scene: Scene; @@ -211,10 +208,10 @@ export class WebController { } } else { if (this.mesh) { - this.diagramManager.onDiagramEventObservable.notifyObservers({ + /*this.diagramManager.onDiagramEventObservable.notifyObservers({ type: DiagramEventType.MODIFY, entity: toDiagramEntity(this.mesh) - }, DiagramEventObserverMask.ALL); + }, DiagramEventObserverMask.ALL); */ } this.mesh = null; } diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts index 768cd79..1bcc69b 100644 --- a/src/diagram/diagramManager.ts +++ b/src/diagram/diagramManager.ts @@ -1,19 +1,14 @@ -import {AbstractMesh, ActionManager, InstancedMesh, Mesh, Observable, Scene} from "@babylonjs/core"; +import {AbstractMesh, ActionManager, Observable, Scene} from "@babylonjs/core"; import {DiagramEntity, DiagramEvent, DiagramEventType} from "./types/diagramEntity"; import log from "loglevel"; import {Controllers} from "../controllers/controllers"; import {AppConfig} from "../util/appConfig"; -import {diagramEventHandler} from "./functions/diagramEventHandler"; -import {deepCopy} from "../util/functions/deepCopy"; -import {applyPhysics} from "./functions/diagramShapePhysics"; -import {applyScaling} from "./functions/applyScaling"; -import {toDiagramEntity} from "./functions/toDiagramEntity"; -import {v4 as uuidv4} from 'uuid'; import {buildEntityActionManager} from "./functions/buildEntityActionManager"; -import {isDiagramEntity} from "./functions/isDiagramEntity"; import {DefaultScene} from "../defaultScene"; import {DiagramMenuManager} from "./diagramMenuManager"; import {DiagramEventObserverMask} from "./types/diagramEventObserverMask"; +import {DiagramObject} from "../objects/diagramObject"; +import {DiagramConnection} from "./diagramConnection"; export class DiagramManager { @@ -24,6 +19,8 @@ export class DiagramManager { public readonly onDiagramEventObservable: Observable = new Observable(); private readonly _diagramMenuManager: DiagramMenuManager; private readonly _scene: Scene; + private readonly _diagramObjects: Map = new Map(); + private readonly _diagramConnections: Map = new Map(); constructor() { this._scene = DefaultScene.Scene; @@ -33,9 +30,6 @@ export class DiagramManager { this._diagramEntityActionManager = buildEntityActionManager(this._controllers); this.onDiagramEventObservable.add(this.onDiagramEvent, DiagramEventObserverMask.FROM_DB, true, this); - this._scene.onMeshRemovedObservable.add((mesh) => { - cleanupOrphanConnections(mesh, this.onDiagramEventObservable); - }); document.addEventListener('uploadImage', (event: CustomEvent) => { const diagramEntity: DiagramEntity = { template: '#image-template', @@ -45,13 +39,17 @@ export class DiagramManager { rotation: {x: 0, y: Math.PI, z: 0}, scale: {x: 1, y: 1, z: 1}, } + const object = new DiagramObject(this._scene, {diagramEntity: diagramEntity}); + this._diagramObjects.set(diagramEntity.id, object); + //const newMesh = buildMeshFromDiagramEntity(diagramEntity, this._scene); if (this.onDiagramEventObservable) { this.onDiagramEventObservable.notifyObservers({ type: DiagramEventType.ADD, entity: diagramEntity - }, DiagramEventObserverMask.ALL); + }, DiagramEventObserverMask.TO_DB); } + }); this.logger.debug("DiagramManager constructed"); } @@ -60,28 +58,24 @@ export class DiagramManager { return this._diagramMenuManager; } + public getDiagramObject(id: string) { + return this._diagramObjects.get(id); + } + + public isDiagramObject(mesh: AbstractMesh) { + return this._diagramObjects.has(mesh?.id) + } + public get controllers(): Controllers { return this._controllers; } - - public createCopy(mesh: AbstractMesh, copy: boolean = false): AbstractMesh { - const newMesh = newInstanceFromMeshOrInstance(mesh); - newMesh.id = 'id' + uuidv4(); - newMesh.actionManager = this._diagramEntityActionManager; - newMesh.position = mesh.absolutePosition.clone(); - if (mesh.absoluteRotationQuaternion) { - newMesh.rotation = mesh.absoluteRotationQuaternion.toEulerAngles().clone(); - } else { - this.logger.error("no rotation quaternion"); + public createCopy(id: string): DiagramObject { + const diagramObject = this._diagramObjects.get(id); + if (!diagramObject) { + return null; } - applyScaling(mesh, newMesh, copy, this._config.current?.createSnap); - newMesh.material = mesh.material; - newMesh.metadata = deepCopy(mesh.metadata); - if (this._config.current?.physicsEnabled) { - applyPhysics(newMesh, this._scene); - } - return newMesh; + return diagramObject.clone(); } public get config(): AppConfig { @@ -90,26 +84,33 @@ export class DiagramManager { private onDiagramEvent(event: DiagramEvent) { - diagramEventHandler( - event, this._scene, this._diagramMenuManager.toolbox, this._config.current.physicsEnabled, - this._diagramEntityActionManager); + switch (event.type) { + case DiagramEventType.ADD: + let diagramObject = this._diagramObjects.get(event.entity.id); + if (diagramObject) { + diagramObject.fromDiagramEntity(event.entity); + } else { + diagramObject = new DiagramObject(this._scene, + {diagramEntity: event.entity, actionManager: this._diagramEntityActionManager}); + } + if (diagramObject) { + this._diagramObjects.set(event.entity.id, diagramObject); + } + break; + case DiagramEventType.REMOVE: + const object = this._diagramObjects.get(event.entity.id); + if (object) { + object.dispose(); + } + this._diagramObjects.delete(event.entity.id); + break; + case DiagramEventType.MODIFY: + console.log(event); + if (event.entity.text) { + const diagramObject = this._diagramObjects.get(event.entity.id); + diagramObject.text = event.entity.text; + } + break; + } } } - -function newInstanceFromMeshOrInstance(mesh: AbstractMesh): AbstractMesh { - if (!mesh.isAnInstance) { - return new InstancedMesh('id' + uuidv4(), (mesh as Mesh)); - } else { - return new InstancedMesh('id' + uuidv4(), (mesh as InstancedMesh).sourceMesh); - } -} - -function cleanupOrphanConnections(mesh: AbstractMesh, diagramEventObservable: Observable) { - if (isDiagramEntity(mesh) && mesh.metadata.template != '#connection-template') { - mesh.getScene().meshes.forEach((m) => { - if (m?.metadata?.to == mesh.id || m?.metadata?.from == mesh.id) { - diagramEventObservable.notifyObservers({type: DiagramEventType.REMOVE, entity: toDiagramEntity(m)}); - } - }); - } -} \ No newline at end of file diff --git a/src/diagram/diagramMenuManager.ts b/src/diagram/diagramMenuManager.ts index a08624b..f14a52c 100644 --- a/src/diagram/diagramMenuManager.ts +++ b/src/diagram/diagramMenuManager.ts @@ -1,7 +1,6 @@ import {DiagramEvent, DiagramEventType} from "./types/diagramEntity"; import {AbstractMesh, ActionEvent, Observable, Scene, Vector3, WebXRInputSource} from "@babylonjs/core"; import {InputTextView} from "../information/inputTextView"; -import {toDiagramEntity} from "./functions/toDiagramEntity"; import {DefaultScene} from "../defaultScene"; import {ControllerEvent, ControllerEventType, Controllers} from "../controllers/controllers"; import log from "loglevel"; @@ -29,19 +28,12 @@ export class DiagramMenuManager { this.configMenu = new ConfigMenu(config); this._inputTextView.onTextObservable.add((evt) => { - const mesh = this._scene.getMeshById(evt.id); - if (mesh) { - const entity = toDiagramEntity(mesh); - entity.text = evt.text; - this.notifyAll({type: DiagramEventType.MODIFY, entity: entity}); - } else { - this.logger.error("mesh not found", evt.id); - } + this.notifyAll({type: DiagramEventType.MODIFY, entity: {id: evt.id, text: evt.text}}); }); this.toolbox = new Toolbox(); this.scaleMenu = new ScaleMenu(); this.scaleMenu.onScaleChangeObservable.add((mesh: AbstractMesh) => { - this.notifyAll({type: DiagramEventType.MODIFY, entity: toDiagramEntity(mesh)}); + this.notifyAll({type: DiagramEventType.MODIFY, entity: {id: mesh.id, scale: mesh.scaling}}); const position = mesh.absolutePosition.clone(); position.y = mesh.getBoundingInfo().boundingBox.maximumWorld.y + .1; this.scaleMenu.changePosition(position); @@ -68,8 +60,6 @@ export class DiagramMenuManager { if (configY > (cameraPos.y - .2)) { this.configMenu.handleMesh.position.y = localCamera.y - .2; } - - } } }); @@ -85,7 +75,7 @@ export class DiagramMenuManager { console.log(evt); switch (evt.source.id) { case "remove": - this.notifyAll({type: DiagramEventType.REMOVE, entity: toDiagramEntity(clickMenu.mesh)}); + this.notifyAll({type: DiagramEventType.REMOVE, entity: {id: clickMenu.mesh.id}}); break; case "label": this.editText(clickMenu.mesh); diff --git a/src/diagram/functions/buildMeshFromDiagramEntity.ts b/src/diagram/functions/buildMeshFromDiagramEntity.ts index 7a5a983..bad6e2b 100644 --- a/src/diagram/functions/buildMeshFromDiagramEntity.ts +++ b/src/diagram/functions/buildMeshFromDiagramEntity.ts @@ -4,14 +4,11 @@ import { InstancedMesh, Mesh, MeshBuilder, - Quaternion, Scene, StandardMaterial, Texture, Vector3 } from "@babylonjs/core"; -import {DiagramConnection} from "../diagramConnection"; -import {updateTextNode} from "../../util/functions/updateTextNode"; import log from "loglevel"; import {v4 as uuidv4} from 'uuid'; import {buildStandardMaterial} from "../../materials/functions/buildStandardMaterial"; @@ -31,6 +28,11 @@ export function buildMeshFromDiagramEntity(entity: DiagramEntity, scene: Scene): generateId(entity); const newMesh: AbstractMesh = createNewInstanceIfNecessary(entity, scene); + + if (!newMesh) { + logger.error("buildMeshFromDiagramEntity: newMesh is null", JSON.stringify((entity))); + return null; + } return mapMetadata(entity, newMesh, scene); } @@ -43,16 +45,13 @@ function createNewInstanceIfNecessary(entity: DiagramEntity, scene: Scene): Abst newMesh = oldMesh; } else { switch (entity.template) { - case DiagramTemplates.CONNECTION: - const connection: DiagramConnection = new DiagramConnection(entity.from, entity.to, entity.id, scene); - logger.debug(`connection.mesh = ${connection.mesh.id}`); - newMesh = connection.mesh; - break; case DiagramTemplates.USER: break; case DiagramTemplates.IMAGE: newMesh = buildImage(entity, scene); - newMesh.metadata = {template: entity.template, exportable: true, tool: false} + break; + case DiagramTemplates.CONNECTION: + newMesh = MeshBuilder.CreateCylinder(entity.id, {diameter: .025, height: 1}, scene); break; case DiagramTemplates.BOX: case DiagramTemplates.SPHERE: @@ -62,7 +61,7 @@ function createNewInstanceIfNecessary(entity: DiagramEntity, scene: Scene): Abst const toolMesh = scene.getMeshById("tool-" + entity.template + "-" + entity.color); if (toolMesh && !oldMesh) { newMesh = new InstancedMesh(entity.id, (toolMesh as Mesh)); - newMesh.metadata = {template: entity.template, exportable: true, tool: false}; + // newMesh.metadata = {template: entity.template, exportable: true, tool: false}; } else { logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color); } @@ -70,6 +69,15 @@ function createNewInstanceIfNecessary(entity: DiagramEntity, scene: Scene): Abst default: logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color); break; + } + if (newMesh) { + if (!newMesh.metadata) { + newMesh.metadata = {template: entity.template, exportable: true, tool: false}; + } else { + newMesh.metadata.template = entity.template; + newMesh.metadata.exportable = true; + newMesh.metadata.tool = false; + } } } @@ -83,8 +91,7 @@ function buildImage(entity: DiagramEntity, scene: Scene): AbstractMesh { const material = new StandardMaterial("planeMaterial", scene); const image = new Image(); image.src = entity.image; - const texture = new Texture(entity.image, scene); - material.emissiveTexture = texture; + material.emissiveTexture = new Texture(entity.image, scene); material.backFaceCulling = false; material.disableLighting = true; plane.material = material; @@ -106,7 +113,7 @@ function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene) if (!newMesh.metadata) { newMesh.metadata = {}; } - if (entity.position) { + /*if (entity.position) { newMesh.position = xyztovec(entity.position); } if (entity.rotation) { @@ -115,25 +122,25 @@ function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene) } else { newMesh.rotation = xyztovec(entity.rotation); } - } - if (entity.parent) { + }*/ + /*if (entity.parent) { const parent_node = scene.getNodeById(entity.parent); if (parent_node) { newMesh.parent = parent_node; newMesh.metadata.parent = entity.parent; } - } - if (entity.scale) { + }*/ + /*if (entity.scale) { newMesh.scaling = xyztovec(entity.scale); - } + }*/ if (!newMesh.material && newMesh?.metadata?.template != "#object-template") { logger.warn("new material created, this shouldn't happen"); newMesh.material = buildStandardMaterial("material-" + entity.id, scene, entity.color); } if (entity.text) { newMesh.metadata.text = entity.text; - updateTextNode(newMesh, entity.text); + //updateTextNode(newMesh, entity.text); } if (entity.from) { newMesh.metadata.from = entity.from; @@ -149,4 +156,8 @@ function mapMetadata(entity: DiagramEntity, newMesh: AbstractMesh, scene: Scene) function xyztovec(xyz: { x, y, z }): Vector3 { return new Vector3(xyz.x, xyz.y, xyz.z); +} + +function vectoxys(v: Vector3): { x, y, z } { + return {x: v.x, y: v.y, z: v.z}; } \ No newline at end of file diff --git a/src/diagram/functions/createLabel.ts b/src/diagram/functions/createLabel.ts new file mode 100644 index 0000000..648553d --- /dev/null +++ b/src/diagram/functions/createLabel.ts @@ -0,0 +1,56 @@ +import {AbstractMesh, Color3, DynamicTexture, Material, MeshBuilder, StandardMaterial} from "@babylonjs/core"; +import {DefaultScene} from "../../defaultScene"; + +function calculateDynamicTextureDimensions(text: string, font: string): { + DTWidth: number, + DTHeight: number, + ratio: number +} { + const temp = new DynamicTexture("DynamicTexture", 32, DefaultScene.Scene); + const tmpctx = temp.getContext(); + tmpctx.font = font; + const DTWidth = tmpctx.measureText(text).width + 8; + temp.dispose(); + + const height = 0.08; + const DTHeight = 1.5 * 24; //or set as wished + const ratio = height / DTHeight; + + return {DTWidth, DTHeight, ratio}; +} + +function createDynamicTexture(text: string, font: string, DTWidth: number, DTHeight: number): DynamicTexture { + const dynamicTexture = new DynamicTexture("DynamicTexture", { + width: DTWidth, + height: DTHeight + }, DefaultScene.Scene, false); + dynamicTexture.drawText(text, null, null, font, "#ffffff", "#000000", true); + return dynamicTexture; +} + +function createMaterial(dynamicTexture: DynamicTexture): Material { + const mat = new StandardMaterial("mat", DefaultScene.Scene); + mat.diffuseColor = Color3.Black(); + mat.disableLighting = true; + mat.backFaceCulling = false; + mat.emissiveTexture = dynamicTexture; + mat.freeze(); + return mat; +} + +function createPlane(text: string, material: Material, planeWidth: number, height: number): AbstractMesh { + const plane = MeshBuilder.CreatePlane("text" + text, {width: planeWidth, height: height}, DefaultScene.Scene); + plane.material = material; + return plane; +} + +export function createLabel(text: string): AbstractMesh { + const font_size = 24; + const font = "bold " + font_size + "px Arial"; + const {DTWidth, DTHeight, ratio} = calculateDynamicTextureDimensions(text, font); + const dynamicTexture = createDynamicTexture(text, font, DTWidth, DTHeight); + const material = createMaterial(dynamicTexture); + const planeWidth = DTWidth * ratio; + const height = 0.08; + return createPlane(text, material, planeWidth, height); +} \ No newline at end of file diff --git a/src/diagram/functions/diagramEventHandler.ts b/src/diagram/functions/diagramEventHandler.ts deleted file mode 100644 index 3e17509..0000000 --- a/src/diagram/functions/diagramEventHandler.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {DiagramEvent, DiagramEventType} from "../types/diagramEntity"; -import log from "loglevel"; -import {applyPhysics} from "./diagramShapePhysics"; -import {ActionManager, PhysicsMotionType, Scene} from "@babylonjs/core"; -import {updateTextNode} from "../../util/functions/updateTextNode"; -import {Toolbox} from "../../toolbox/toolbox"; - -import {buildMeshFromDiagramEntity} from "./buildMeshFromDiagramEntity"; -import {isDiagramEntity} from "./isDiagramEntity"; - - -export function diagramEventHandler(event: DiagramEvent, - scene: Scene, - toolbox: Toolbox, - physicsEnabled: boolean, - actionManager: ActionManager) { - const entity = event.entity; - let mesh; - if (event.type == DiagramEventType.REMOVE) { - mesh = scene.getMeshById(entity.id); - } else { - if (event?.entity?.template) { - const toolMesh = scene.getMeshById("tool-" + event.entity.template + "-" + event.entity.color); - if (!toolMesh && event.type != DiagramEventType.CHANGECOLOR) { - log.debug('no mesh found for ' + event.entity.template + "-" + event.entity.color, 'adding it'); - toolbox.updateToolbox(event.entity.color); - } - mesh = buildMeshFromDiagramEntity(event.entity, scene); - if (mesh) { - mesh.actionManager = actionManager; - if (physicsEnabled) { - applyPhysics(mesh, scene, PhysicsMotionType.DYNAMIC); - } - } - } - } - if (isDiagramEntity(mesh) && (mesh.metadata.template.indexOf('#') > -1)) { - updateTextNode(mesh, entity.text); - } - switch (event.type) { - case DiagramEventType.RESET: - scene.getNodes().forEach((node) => { - if (node?.metadata?.template && !node?.metadata?.tool) { - node.dispose(); - } - }); - break; - case DiagramEventType.CLEAR: - break; - case DiagramEventType.DROPPED: - break; - case DiagramEventType.DROP: - if (isDiagramEntity(mesh) && (mesh.metadata.template.indexOf('#') > -1)) { - updateTextNode(mesh, entity.text); - } - break; - case DiagramEventType.ADD: - if (mesh && !mesh.actionManager) { - mesh.actionManager = actionManager; - } - if (physicsEnabled) { - applyPhysics(mesh, scene); - } - break; - case DiagramEventType.MODIFY: - if (mesh && physicsEnabled) { - applyPhysics(mesh, scene); - } - break; - case DiagramEventType.REMOVE: - if (mesh) { - mesh?.physicsBody?.dispose(); - if (mesh?.metadata?.template == '#connection-template') { - if (mesh.parent) { - mesh.parent.dispose(); - } else { - mesh.dispose(); - } - } else { - mesh.dispose(); - } - - } - break; - } -} diff --git a/src/diagram/functions/toDiagramEntity.ts b/src/diagram/functions/toDiagramEntity.ts index 35d78cf..589d10a 100644 --- a/src/diagram/functions/toDiagramEntity.ts +++ b/src/diagram/functions/toDiagramEntity.ts @@ -15,8 +15,8 @@ export function toDiagramEntity(mesh: AbstractMesh): DiagramEntity { mesh.id = "id" + uuidv4(); } entity.id = mesh.id; - entity.position = vectoxys(mesh.position); - entity.rotation = vectoxys(mesh.rotation); + entity.position = vectoxys(mesh.absolutePosition); + entity.rotation = vectoxys(mesh.absoluteRotationQuaternion.toEulerAngles()); entity.last_seen = new Date(); entity.template = mesh?.metadata?.template; entity.text = mesh?.metadata?.text; diff --git a/src/diagram/types/diagramEntity.ts b/src/diagram/types/diagramEntity.ts index e521949..c03ab39 100644 --- a/src/diagram/types/diagramEntity.ts +++ b/src/diagram/types/diagramEntity.ts @@ -54,4 +54,5 @@ export type DiagramEntity = { scale?: { x: number, y: number, z: number }; parent?: string; diagramlistingid?: string; + friendly?: string; } \ No newline at end of file diff --git a/src/diagram/types/meshTypeEnum.ts b/src/diagram/types/meshTypeEnum.ts new file mode 100644 index 0000000..6ac8dcc --- /dev/null +++ b/src/diagram/types/meshTypeEnum.ts @@ -0,0 +1,5 @@ +export enum MeshTypeEnum { + TOOL, + HANDLE, + ENTITY +} \ No newline at end of file diff --git a/src/integration/pouchdbPersistenceManager.ts b/src/integration/pouchdbPersistenceManager.ts index f3ec639..8a37f90 100644 --- a/src/integration/pouchdbPersistenceManager.ts +++ b/src/integration/pouchdbPersistenceManager.ts @@ -40,10 +40,13 @@ export class PouchdbPersistenceManager { this.onDBUpdateObservable.add((evt) => { this.logger.debug(evt); - diagramManager.onDiagramEventObservable.notifyObservers({ - type: DiagramEventType.ADD, - entity: evt - }, DiagramEventObserverMask.FROM_DB); + if (!evt.friendly) { + diagramManager.onDiagramEventObservable.notifyObservers({ + type: DiagramEventType.ADD, + entity: evt + }, DiagramEventObserverMask.FROM_DB); + } + }); this.onDBRemoveObservable.add((entity) => { @@ -140,11 +143,20 @@ export class PouchdbPersistenceManager { } private async sendLocalDataToScene() { + const clear = localStorage.getItem('clearLocal'); try { const all = await this.db.allDocs({include_docs: true}); for (const entity of all.rows) { this.logger.debug(entity.doc); - this.onDBUpdateObservable.notifyObservers(entity.doc, 1); + if (clear) { + this.remove(entity.id); + } else { + this.onDBUpdateObservable.notifyObservers(entity.doc, 1); + } + + } + if (clear) { + localStorage.removeItem('clearLocal'); } } catch (err) { this.logger.error(err); diff --git a/src/menus/clickMenu.ts b/src/menus/clickMenu.ts index fffedf9..653a19a 100644 --- a/src/menus/clickMenu.ts +++ b/src/menus/clickMenu.ts @@ -1,8 +1,5 @@ -import {AbstractMesh, ActionEvent, Observable, Scene, TransformNode, Vector3, WebXRInputSource} from "@babylonjs/core"; -import {DiagramEvent, DiagramEventType} from "../diagram/types/diagramEntity"; -import {toDiagramEntity} from "../diagram/functions/toDiagramEntity"; -import {DiagramConnection} from "../diagram/diagramConnection"; -import {isDiagramEntity} from "../diagram/functions/isDiagramEntity"; +import {AbstractMesh, ActionEvent, Observable, Scene, TransformNode, WebXRInputSource} from "@babylonjs/core"; +import {DiagramEvent, DiagramEventType, DiagramTemplates} from "../diagram/types/diagramEntity"; import {HtmlButton} from "babylon-html"; import {DiagramEventObserverMask} from "../diagram/types/diagramEventObserverMask"; @@ -11,23 +8,31 @@ const POINTER_UP = "pointerup"; export class ClickMenu { private readonly _mesh: AbstractMesh; private readonly transform: TransformNode; - private connection: DiagramConnection = null; - public onClickMenuObservable: Observable = new Observable(); private _diagramEventObservable: Observable; + private connectFromId: string = null; + + private getTransform(input: WebXRInputSource | TransformNode): TransformNode { + if (input == null) return null; + if ('grip' in input) { + return input.grip; + } else { + return input as TransformNode; + } + } + constructor(mesh: AbstractMesh, input: WebXRInputSource | TransformNode, diagramEventObservable: Observable) { const grip: TransformNode = this.getTransform(input); this._mesh = mesh; this._diagramEventObservable = diagramEventObservable; - //this.diagramManager = diagramManager; const scene = mesh.getScene(); this.transform = new TransformNode("transform", scene); let x = -.54 / 2; - const removeButton: HtmlButton = this.makeNewButton("Remove", "remove", scene, x += .11); - removeButton.onPointerObservable.add((eventData) => { + this.makeNewButton("Remove", "remove", scene, x += .11) + .onPointerObservable.add((eventData) => { if (isUp(eventData)) { this.onClickMenuObservable.notifyObservers(eventData); this.dispose(); @@ -35,31 +40,32 @@ export class ClickMenu { }, -1, false, this, false); - const labelButton: HtmlButton = this.makeNewButton("Label", "label", scene, x += .11); - labelButton.onPointerObservable.add((eventData) => { + this.makeNewButton("Label", "label", scene, x += .11) + .onPointerObservable.add((eventData) => { if (isUp(eventData)) { this.onClickMenuObservable.notifyObservers(eventData); this.dispose(); } }, -1, false, this, false); - const connectButton: HtmlButton = this.makeNewButton("Connect", "connect", scene, x += .11); - connectButton.onPointerObservable.add((eventData) => { + this.makeNewButton("Connect", "connect", scene, x += .11) + .onPointerObservable.add((eventData) => { if (isUp(eventData)) { - this.createMeshConnection(this._mesh, grip, eventData.additionalData.pickedPoint.clone()); + this.connectFromId = this._mesh.id; + //this.createMeshConnection(this._mesh, grip, eventData.additionalData.pickedPoint.clone()); } }, -1, false, this, false); - const closeButton: HtmlButton = this.makeNewButton("Close", "close", scene, x += .11); - closeButton.onPointerObservable.add((eventData) => { + this.makeNewButton("Close", "close", scene, x += .11) + .onPointerObservable.add((eventData) => { if (isUp(eventData)) { this.onClickMenuObservable.notifyObservers(eventData); this.dispose(); } }, -1, false, this, false); - const sizeButton: HtmlButton = this.makeNewButton("Size", "size", scene, x += .11); - sizeButton.onPointerObservable.add((eventData) => { + this.makeNewButton("Size", "size", scene, x += .11) + .onPointerObservable.add((eventData) => { if (isUp(eventData)) { this.onClickMenuObservable.notifyObservers(eventData); } @@ -73,27 +79,27 @@ export class ClickMenu { this.transform.rotation.z = 0; } - private getTransform(input: WebXRInputSource | TransformNode): TransformNode { - if (input == null) return null; - if ('grip' in input) { - return input.grip; - } else { - return input as TransformNode; + public get isConnecting() { + return this.connectFromId != null; + } + + public connect(mesh: AbstractMesh) { + if (this.isConnecting) { + if (mesh) { + this._diagramEventObservable.notifyObservers({ + type: DiagramEventType.ADD, + entity: { + from: this.connectFromId, + to: mesh.id, + template: DiagramTemplates.CONNECTION, + color: '#000000' + } + }, DiagramEventObserverMask.ALL); + this.connectFromId = null; + this.dispose(); + } } } - - private makeNewButton(name: string, id: string, scene: Scene, x: number): HtmlButton { - const button = new HtmlButton(name, id, scene, null, {html: null, image: {width: 268, height: 268}}); - button.transform.parent = this.transform; - button.transform.rotation.y = Math.PI; - button.transform.position.x = x; - return button; - } - - public get isConnecting() { - return this.connection != null; - } - public get isDisposed(): boolean { return this.transform.isDisposed(); } @@ -101,22 +107,14 @@ export class ClickMenu { public get mesh(): AbstractMesh { return this._mesh; } - public connect(mesh: AbstractMesh) { - if (this.connection) { - if (mesh && isDiagramEntity(mesh)) { - this.connection.to = mesh.id; - this._diagramEventObservable.notifyObservers({ - type: DiagramEventType.ADD, - entity: toDiagramEntity(this.connection.mesh) - }, DiagramEventObserverMask.ALL); - this.connection = null; - this.dispose(); - } - } - } - private createMeshConnection(mesh: AbstractMesh, grip: TransformNode, utilityPosition: Vector3) { - this.connection = new DiagramConnection(mesh.id, null, null, this.transform.getScene(), grip, utilityPosition); + private makeNewButton(name: string, id: string, scene: Scene, x: number): HtmlButton { + const button = new HtmlButton(name, id, scene, null, {html: null, image: {width: 268, height: 268}}); + const transform = button.transform; + transform.parent = this.transform; + transform.rotation.y = Math.PI; + transform.position.x = x; + return button; } private dispose() { diff --git a/src/objects/diagramObject.ts b/src/objects/diagramObject.ts index b5a81f9..c2562eb 100644 --- a/src/objects/diagramObject.ts +++ b/src/objects/diagramObject.ts @@ -1,55 +1,172 @@ -import {AbstractMesh, Scene} from "@babylonjs/core"; +import {AbstractActionManager, AbstractMesh, Mesh, Observer, Scene, TransformNode, Vector3} from "@babylonjs/core"; import {DiagramEntity} from "../diagram/types/diagramEntity"; import {buildMeshFromDiagramEntity} from "../diagram/functions/buildMeshFromDiagramEntity"; import {toDiagramEntity} from "../diagram/functions/toDiagramEntity"; +import {v4 as uuidv4} from 'uuid'; +import {createLabel} from "../diagram/functions/createLabel"; type DiagramObjectOptionsType = { diagramEntity?: DiagramEntity, - mesh?: AbstractMesh + mesh?: AbstractMesh, + actionManager?: AbstractActionManager } + export class DiagramObject { private _scene: Scene; + private _from: string; + private _to: string; + private _observingStart: number; + private _sceneObserver: Observer; + private _observer: Observer; + + private _mesh: AbstractMesh; + private _label: AbstractMesh; + public get mesh(): AbstractMesh { + return this._mesh; + } constructor(scene: Scene, options?: DiagramObjectOptionsType) { this._scene = scene; if (options) { if (options.diagramEntity) { - this.fromDiagramEntity(options.diagramEntity); + if (!options.diagramEntity.id) { + options.diagramEntity.id = 'id' + uuidv4(); + } + const myEntity = this.fromDiagramEntity(options.diagramEntity); + if (!myEntity) { + return null; + } } if (options.mesh) { this._mesh = options.mesh; this._diagramEntity = this.diagramEntity; } + if (options.actionManager && this._mesh) { + this._mesh.actionManager = options.actionManager; + } } } - private _mesh: AbstractMesh; - - public get mesh(): AbstractMesh { - return this._mesh; - } + private _baseTransform: TransformNode; private _diagramEntity: DiagramEntity; + public get baseTransform() { + return this._baseTransform; + } + public get diagramEntity(): DiagramEntity { - if (!this._diagramEntity) { - if (this._mesh) { - this._diagramEntity = toDiagramEntity(this._mesh); - } + if (this._mesh) { + this._diagramEntity = toDiagramEntity(this._mesh); + } return this._diagramEntity; } + public set text(value: string) { + if (this._label) { + this._label.dispose(); + } + this._label = createLabel(value); + this._label.parent = this._baseTransform; + this.updateLabelPosition(); + } + + public updateLabelPosition() { + if (this._label) { + this._mesh.computeWorldMatrix(true); + this._mesh.refreshBoundingInfo(); + const top = + this._mesh.getBoundingInfo().boundingBox.maximumWorld; + const temp = new TransformNode("temp", this._scene); + temp.position = top; + temp.setParent(this._baseTransform); + const y = temp.position.y; + temp.dispose(); + this._label.position.y = y + .06; + this._label.billboardMode = Mesh.BILLBOARDMODE_Y; + } + } + + public clone(): DiagramObject { + const clone = new DiagramObject(this._scene, {actionManager: this._mesh.actionManager}); + const newEntity = {...this._diagramEntity}; + newEntity.id = 'id' + uuidv4(); + clone.fromDiagramEntity(this._diagramEntity); + + return clone; + } + public fromDiagramEntity(entity: DiagramEntity): DiagramObject { this._diagramEntity = entity; - this._mesh = buildMeshFromDiagramEntity(this._diagramEntity, this._scene); + if (!this._mesh) { + this._mesh = buildMeshFromDiagramEntity(this._diagramEntity, this._scene); + } + if (!this._mesh) { + return null; + } + if (entity.from) { + this._from = entity.from; + } + if (entity.to) { + this._to = entity.to; + } + if (!this._baseTransform) { + this._baseTransform = new TransformNode("base-" + this._mesh.id, this._scene); + } + + if (this._from && this._to) { + if (!this._sceneObserver) { + this._observingStart = Date.now(); + this._sceneObserver = this._scene.onAfterRenderObservable.add(() => { + const fromMesh = this._scene.getMeshById(this._from); + const toMesh = this._scene.getMeshById(this._to); + if (fromMesh && toMesh) { + this.updateConnection(fromMesh, toMesh); + } else { + if (Date.now() - this._observingStart > 5000) { + this.dispose(); + } + } + }, -1, false, this); + } + } else { + this._mesh.setParent(this._baseTransform); + this._baseTransform.position = xyztovec(entity.position); + this._baseTransform.rotation = xyztovec(entity.rotation); + this._mesh.scaling = xyztovec(entity.scale); + this._mesh.position = Vector3.Zero(); + this._mesh.rotation = Vector3.Zero(); + } + + if (entity.text) { + this.text = entity.text; + } return this; } public dispose() { - this._mesh.dispose(false, true); + this._scene.onAfterRenderObservable.remove(this._sceneObserver); + this._sceneObserver = null; + this._mesh?.dispose(false, true); this._mesh = null; + this._label?.dispose(); + this._label = null; this._diagramEntity = null; this._scene = null; } + + private updateConnection(fromMesh: AbstractMesh, toMesh: AbstractMesh) { + this._baseTransform.position = Vector3.Center(fromMesh.getAbsolutePosition().clone(), toMesh.getAbsolutePosition().clone()); + this._baseTransform.lookAt(toMesh.getAbsolutePosition()); + this._mesh.scaling.y = Vector3.Distance(fromMesh.getAbsolutePosition(), toMesh.getAbsolutePosition()); + if (!this._mesh.parent) { + this._mesh.parent = this._baseTransform; + } + this._mesh.rotation.x = Math.PI / 2; + } +} + +function xyztovec(xyz: { x, y, z }): Vector3 { + return new Vector3(xyz.x, xyz.y, xyz.z); } \ No newline at end of file diff --git a/src/toolbox/functions/buildColor.ts b/src/toolbox/functions/buildColor.ts index 8c57c46..4e332da 100644 --- a/src/toolbox/functions/buildColor.ts +++ b/src/toolbox/functions/buildColor.ts @@ -1,9 +1,18 @@ -import {Color3, MeshBuilder, Node, Scene, StandardMaterial, TransformNode, Vector3} from "@babylonjs/core"; +import { + AbstractMesh, + Color3, + MeshBuilder, + Node, + Scene, + StandardMaterial, + TransformNode, + Vector3 +} from "@babylonjs/core"; import {enumKeys} from "../../util/functions/enumKeys"; import {ToolType} from "../types/toolType"; import {buildTool} from "./buildTool"; -export function buildColor(color: Color3, scene: Scene, parent: TransformNode, index: number): Node { +export function buildColor(color: Color3, scene: Scene, parent: TransformNode, index: number, toolMap: Map): Node { const width = .1; const height = .1; const material = new StandardMaterial("material-" + color.toHexString(), scene); @@ -34,6 +43,7 @@ export function buildColor(color: Color3, scene: Scene, parent: TransformNode, i //buildColorPicker(scene, color, newItem, material, i, colorChangeObservable); newItem.position = new Vector3(calculatePosition(++i), .1, 0); tools.push(newItem.id); + toolMap.set(newItem.id, newItem); } } colorBoxMesh.metadata.tools = tools; diff --git a/src/toolbox/functions/buildTool.ts b/src/toolbox/functions/buildTool.ts index 17c31ab..f3d5c89 100644 --- a/src/toolbox/functions/buildTool.ts +++ b/src/toolbox/functions/buildTool.ts @@ -6,12 +6,15 @@ const WIDGET_SIZE = .1; export function buildTool(tool: ToolType, colorParent: AbstractMesh, material: Material) { let id = "ID"; + let color = "#000000"; switch (material.getClassName()) { case "StandardMaterial": id = toolId(tool, (material as StandardMaterial).diffuseColor); + color = (material as StandardMaterial).diffuseColor.toHexString(); break; case "PBRMaterial": id = toolId(tool, (material as PBRMaterial).albedoColor); + color = (material as PBRMaterial).albedoColor.toHexString(); break; default: this.logger.warn("buildTool: parent.material is null"); @@ -30,9 +33,9 @@ export function buildTool(tool: ToolType, colorParent: AbstractMesh, material: M WIDGET_SIZE, WIDGET_SIZE); newItem.parent = colorParent.parent; - newItem.metadata = {template: tool, tool: true, grabClone: true}; - const instance = new InstancedMesh("instance-" + id, newItem); - instance.metadata = {template: tool, tool: true, grabClone: true}; + newItem.metadata = {template: tool, tool: true, grabClone: true, color: color}; + const instance = new InstancedMesh("tool-instance-" + id, newItem); + instance.metadata = {template: tool, tool: true, grabClone: true, color: color}; instance.parent = colorParent.parent; instance.setEnabled(false); newItem.setEnabled(false); diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index 4172957..23a1ac9 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -1,5 +1,4 @@ -import {AbstractMesh, AxesViewer, Color3, Node, Observable, Scene, TransformNode, Vector3} from "@babylonjs/core"; -import {GUI3DManager, StackPanel3D,} from "@babylonjs/gui"; +import {AbstractMesh, Color3, InstancedMesh, Node, Scene, TransformNode, Vector3} from "@babylonjs/core"; import {buildColor} from "./functions/buildColor"; import log from "loglevel"; import {Handle} from "../objects/handle"; @@ -14,6 +13,17 @@ const colors: string[] = [ export class Toolbox { + private readonly tools: Map = new Map(); + + constructor() { + this.scene = DefaultScene.Scene; + this.toolboxBaseNode = new TransformNode("toolbox", this.scene); + this.handle = new Handle(this.toolboxBaseNode); + this.toolboxBaseNode.position.y = .2; + this.toolboxBaseNode.scaling = new Vector3(0.6, 0.6, 0.6); + this.buildToolbox(); + Toolbox._instance = this; + } private readonly logger = log.getLogger('Toolbox'); private index = 0; public readonly toolboxBaseNode: TransformNode; @@ -21,45 +31,25 @@ export class Toolbox { 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 }> = - new Observable<{ oldColor: string; newColor: string }>() - private axes: AxesViewer; - constructor() { - this.scene = DefaultScene.Scene; - this.addPanel = new StackPanel3D(); - this.manager = new GUI3DManager(this.scene); - this.manager.addControl(this.addPanel); - this.toolboxBaseNode = new TransformNode("toolbox", this.scene); - this.handle = new Handle(this.toolboxBaseNode); - this.toolboxBaseNode.position.y = .2; - this.toolboxBaseNode.scaling = new Vector3(0.6, 0.6, 0.6); - this.buildToolbox(); + public static _instance; + + public static get instance() { + return Toolbox._instance; } - public updateToolbox(color: string) { - if (color) { - if (this.scene.getMeshById("toolbox-color-" + color)) { - return; - } else { - buildColor(Color3.FromHexString(color), this.scene, this.toolboxBaseNode, this.index++); - } - } else { - this.logger.warn("updateToolbox called with no color"); - } - + public isTool(mesh: AbstractMesh) { + return this.tools.has(mesh.id); } - private nodePredicate = (node: Node) => { return node.getClassName() == "InstancedMesh" && node.isEnabled(false) == true }; - private buildToolbox() { + private setupPointerObservable() { this.scene.onPointerObservable.add((pointerInfo) => { - if (pointerInfo.type == 1 && pointerInfo.pickInfo.pickedMesh?.metadata?.tool == 'color') { + if (pointerInfo.type == 1 && + pointerInfo.pickInfo.pickedMesh?.metadata?.tool == 'color') { if (this.changing) { this.colorPicker.setEnabled(true); return; @@ -77,9 +67,12 @@ export class Toolbox { } } }); + } + + private buildColorPicker() { let initial = true; for (const c of colors) { - const cnode = buildColor(Color3.FromHexString(c), this.scene, this.toolboxBaseNode, this.index++); + const cnode = buildColor(Color3.FromHexString(c), this.scene, this.toolboxBaseNode, this.index++, this.tools); if (initial) { initial = false; for (const id of cnode.metadata.tools) { @@ -89,41 +82,38 @@ export class Toolbox { } } + } + + private assignHandleParentAndStore(mesh: TransformNode) { const offset = new Vector3(-.50, 1.6, .38); const rotation = new Vector3(.5, -.6, .18); + const handle = this.handle; + handle.mesh.parent = mesh; + if (!handle.idStored) { + handle.mesh.position = offset; + handle.mesh.rotation = rotation; + } + + } + + private buildToolbox() { + this.setupPointerObservable(); + this.buildColorPicker(); if (this.toolboxBaseNode.parent) { - const platform = this.scene.getNodeById("platform"); - + const platform = this.scene.getMeshById("platform"); if (platform) { - const handle = this.handle; - handle.mesh.parent = platform; - if (!handle.idStored) { - handle.mesh.position = offset; - handle.mesh.rotation = rotation; - } - + this.assignHandleParentAndStore(platform); } else { - this.scene.onNewMeshAddedObservable.add((mesh: AbstractMesh) => { + const observer = this.scene.onNewMeshAddedObservable.add((mesh: AbstractMesh) => { if (mesh && mesh.id == "platform") { - const handle = this.handle; - handle.mesh.parent = mesh; - if (!handle.idStored) { - handle.mesh.position = offset; - handle.mesh.rotation = rotation; - } - //handle.mesh.parent = mesh; - + this.assignHandleParentAndStore(mesh); + this.scene.onNewMeshAddedObservable.remove(observer); } }, -1, false, this, false); } - } - - /*setMenuPosition(this.toolboxBaseNode.parent as Mesh, this.scene, - Vector3.Zero());*/ } - public get handleMesh(): AbstractMesh { return this.handle.mesh; }