import {AbstractMesh, MeshBuilder, Scene, StandardMaterial, TransformNode, Vector3} from "@babylonjs/core"; import {v4 as uuidv4} from 'uuid'; import log, {Logger} from "loglevel"; import {buildStandardMaterial} from "../materials/functions/buildStandardMaterial"; export class DiagramConnection { private readonly id: string; private logger: Logger = log.getLogger('DiagramConnection'); constructor(from: string, to: string, id: string, scene?: Scene, gripTransform?: TransformNode, clickPoint?: Vector3) { this.logger.debug('buildConnection constructor'); if (id) { this.id = id; } else { this.id = "connection_" + uuidv4(); } this.scene = scene; this._to = to; this._from = from; const fromMesh = this.scene.getMeshById(from); if (fromMesh) { this.fromAnchor = fromMesh; } const toMesh = this.scene.getMeshById(to); if (toMesh) { this.toAnchor = toMesh; } else { if (fromMesh) { const to = new TransformNode(this.id + "_to", this.scene); to.ignoreNonUniformScaling = true; to.id = this.id + "_to"; if (clickPoint) { to.position = clickPoint.clone(); } else { to.position = fromMesh.absolutePosition.clone(); } if (gripTransform) { to.setParent(gripTransform); } this.toAnchor = to; } else { this.logger.info("no fromMesh yet, will build when toMesh is available"); } } this.buildConnection(); } private scene: Scene; private toAnchor: TransformNode; private fromAnchor: TransformNode; private transformNode: TransformNode; private _mesh: AbstractMesh; public get mesh(): AbstractMesh { return this._mesh; } private readonly _to: string; public get to(): string { return this?.toAnchor?.id; } public set to(value: string) { if (!value) { return; } const toAnchor = this.scene.getMeshById(value); if (this.fromAnchor && toAnchor) { this.toAnchor.dispose(); this.toAnchor = toAnchor; this._mesh.metadata.to = this.to; this._mesh.metadata.exportable = true; this._mesh.id = this.id; this.recalculate(); this.setPoints(); } } private readonly _from: string; public get from(): string { return this?.fromAnchor?.id; } private tick: number = 0; private recalculate() { const start = this.fromAnchor?.absolutePosition; const end = this.toAnchor?.absolutePosition; if (start && end) { this.transformNode.position = start.add(end).scale(.5); this.transformNode.lookAt(end); this._mesh.rotation.x = Math.PI / 2; this._mesh.scaling.y = Math.abs(start.subtract(end).length()); const text = this._mesh.getChildren((node) => { return node.metadata?.label == true; }); if (text && text.length > 0) { text.forEach((node) => { const t: AbstractMesh = node as AbstractMesh; t.scaling.y = 1 / this._mesh.scaling.y; t.position.x = .05; t.position.z = .05; t.position.y = 0; }); } if (this.fromAnchor && (this.fromAnchor as AbstractMesh).material) { this._mesh.material = (((this.fromAnchor as AbstractMesh).material as StandardMaterial)); } else { this._mesh.material = buildStandardMaterial(this.id + "_material", this.scene, "#FFFFFF"); } } } private setPoints() { } private buildConnection() { this.logger.debug(`buildConnection from ${this._from} to ${this._to}`); this._mesh = MeshBuilder.CreateCylinder(this.id + "_connection", {diameter: .025, height: 1}, this.scene); this.transformNode = new TransformNode(this.id + "_transform", this.scene); this.transformNode.metadata = {exportable: true}; this._mesh.setParent(this.transformNode); this.recalculate(); this._mesh.id = this.id; if (!this._mesh.metadata) { this._mesh.metadata = {template: "#connection-template", from: this._from}; } else { this._mesh.metadata.template = "#connection-template"; this._mesh.metadata.from = this._from; } if (this._to) { this._mesh.metadata.to = this.to; } this._mesh.metadata.exportable = true; this.setPoints(); this.scene.onBeforeRenderObservable.add(this.beforeRender, -1, true, this); this.scene.onNewMeshAddedObservable.add(this.onMeshAdded, -1, true, this); this.mesh.onDisposeObservable.add(this.removeConnection, -1, true, this); return; } private beforeRender = () => { if (this.tick++ % 10 == 0) { this.logger.trace('recalculating', this.tick); this.recalculate(); this.setPoints(); } } private removeConnection = () => { this.logger.debug("removeConnection"); this.scene.onBeforeRenderObservable.removeCallback(this.beforeRender); this._mesh.onDisposeObservable.removeCallback(this.removeConnection); this.removeObserver(); if (this.toAnchor) { this.toAnchor = null; } if (this.fromAnchor) { this.fromAnchor = null; } if (this._mesh) { this._mesh.dispose(); this._mesh = null; } if (this.scene) { this.scene = null; } } private onMeshAdded = (mesh: AbstractMesh) => { if (mesh && mesh.id) { if (!this.toAnchor || !this.fromAnchor) { if (mesh?.id == this?._to) { this.logger.debug("Found to anchor"); this.toAnchor = mesh; this._mesh.metadata.to = this.to; } if (mesh?.id == this?._from) { this.logger.debug("Found from anchor"); this.fromAnchor = mesh; this._mesh.metadata.from = this.from; } if (this.toAnchor && this.fromAnchor) { this.logger.debug(`connection built from ${this._from} to ${this._to}`); this.removeObserver(); } } } } private removeObserver() { this.logger.debug("removing observer"); this.scene.onNewMeshAddedObservable.removeCallback(this.onMeshAdded); } }