Updated basic grab/drop.

This commit is contained in:
Michael Mainguy 2024-05-28 15:33:44 -05:00
parent 2872026ac9
commit e85adc1386
24 changed files with 572 additions and 465 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -10,4 +10,4 @@ export function motionControllerObserver(init) {
if (init.components['xr-standard-trigger']) {
this.initClicker(init.components['xr-standard-trigger']);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<DiagramEvent> = new Observable();
private readonly _diagramMenuManager: DiagramMenuManager;
private readonly _scene: Scene;
private readonly _diagramObjects: Map<string, DiagramObject> = new Map<string, DiagramObject>();
private readonly _diagramConnections: Map<string, DiagramConnection> = new Map<string, DiagramConnection>();
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<DiagramEvent>) {
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)});
}
});
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -54,4 +54,5 @@ export type DiagramEntity = {
scale?: { x: number, y: number, z: number };
parent?: string;
diagramlistingid?: string;
friendly?: string;
}

View File

@ -0,0 +1,5 @@
export enum MeshTypeEnum {
TOOL,
HANDLE,
ENTITY
}

View File

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

View File

@ -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<ActionEvent> = new Observable<ActionEvent>();
private _diagramEventObservable: Observable<DiagramEvent>;
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<DiagramEvent>) {
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() {

View File

@ -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<Scene>;
private _observer: Observer<AbstractMesh>;
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);
}

View File

@ -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<string, AbstractMesh>): 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;

View File

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

View File

@ -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<string, InstancedMesh> = new Map<string, InstancedMesh>();
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;
}