import { AbstractMesh, Mesh, PhysicsMotionType, Scene, TransformNode, Vector3, WebXRControllerComponent, WebXRDefaultExperience, WebXRInputSource } from "@babylonjs/core"; import {DiagramEventObserverMask, 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"; const CLICK_TIME = 300; const logger = log.getLogger('Base'); export class Base { static stickVector = Vector3.Zero(); protected xrInputSource: WebXRInputSource; protected speedFactor = 4; protected readonly scene: Scene; protected grabbedMesh: AbstractMesh = null; protected grabbedMeshParentId: string = null; protected previousParentId: string = null; protected previousRotation: Vector3 = null; protected previousScaling: Vector3 = null; protected previousPosition: Vector3 = 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(); constructor(controller: WebXRInputSource, xr: WebXRDefaultExperience, diagramManager: DiagramManager) { this.xrInputSource = controller; this.controllers = diagramManager.controllers; 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); } }); 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); this.controllers.controllerObserver.add((event) => { logger.debug(event); switch (event.type) { case ControllerEventType.PULSE: if (event.gripId == this?.xrInputSource?.grip?.id) { this.xrInputSource?.motionController?.pulse(.25, 30) .then(() => { logger.debug("pulse done"); }); } break; case ControllerEventType.HIDE: this.disable(); break; case ControllerEventType.SHOW: this.enable(); break; } }); } public disable() { this.scene.preventDefaultOnPointerDown = true; this.xrInputSource.motionController.rootMesh.setEnabled(false) this.xrInputSource.pointer.setEnabled(false); } public enable() { this.scene.preventDefaultOnPointerDown = false; this.xrInputSource.motionController.rootMesh.setEnabled(true); this.xrInputSource.pointer.setEnabled(true) } protected initClicker(trigger: WebXRControllerComponent) { logger.debug("initTrigger"); trigger.onButtonStateChangedObservable.add(() => { if (trigger.changes.pressed) { if (trigger.pressed) { if (this.clickStart == 0) { this.clickStart = Date.now(); window.setTimeout(() => { if (this.clickStart > 0) { logger.debug("grabbing and cloning"); this.grab(true); } }, 300, this); } } else { const clickEnd = Date.now(); if (this.clickStart > 0 && (clickEnd - this.clickStart) < CLICK_TIME) { this.click(); } this.drop(); this.clickStart = 0; } } }, -1, false, this); } private grab(cloneEntity: boolean = false) { let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId); if (!mesh) { return; } let player = false; displayDebug(mesh); if (!isDiagramEntity(mesh)) { if (handleWasGrabbed(mesh)) { mesh && mesh.setParent(this.xrInputSource.motionController.rootMesh); this.grabbedMesh = mesh; } else { if (mesh?.parent?.parent?.metadata?.grabbable) { if (mesh?.parent?.parent?.parent) { mesh = (mesh?.parent?.parent?.parent as Mesh); this.grabbedMesh = mesh; player = true; } } else { return; } } } else { if (mesh?.metadata?.template == '#connection-template') { return; } } this.previousParentId = mesh?.parent?.id; 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 { 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); } } private drop() { const mesh = this.grabbedMesh; 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) //transform.dispose(); } 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); logger.debug(body.transformNode.absolutePosition); 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)); 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)) { logger.debug("click on " + mesh.id); if (this.clickMenu && !this.clickMenu.isDisposed) { if (this.clickMenu.isConnecting) { this.clickMenu.connect(mesh); this.clickMenu = null; } } else { this.clickMenu = this.diagramManager.diagramMenuManager.createClickMenu(mesh, this.xrInputSource.grip); } } else { logger.debug("click on nothing"); } } private initGrip(grip: WebXRControllerComponent) { grip.onButtonStateChangedObservable.add(() => { if (grip.changes.pressed) { if (grip.pressed) { this.grab(); } else { this.drop(); } } }); } }